Repository: apenella/go-ansible

Issue:
Many ssh <defunct>  then using library. 
Good afternoon. I think I found a problem in the library, a large number of child processes are not closed. Here is my example

`func ansible() error {
	fmt.Println("test")
	ansiblePlaybookConnectionOptions := &options.AnsibleConnectionOptions{
		User:       "inurmuhametov",
		SSHExtraArgs: "-o ControlPersist=360s",
		SCPExtraArgs: "-o ControlPersist=360s",
		SFTPExtraArgs: "-o ControlPersist=360s",
	}

	ansiblePlaybookOptions := &playbook.AnsiblePlaybookOptions{
		Inventory:  "<server>,",
		VerboseVVVV:true,
	}

	playbook := &playbook.AnsiblePlaybookCmd{
		Playbooks:         []string{
			"/tmp/performance.yml"},
		ConnectionOptions: ansiblePlaybookConnectionOptions,
		Options:           ansiblePlaybookOptions,
	}

	err := playbook.Run(context.TODO())
	if err !=nil {
		playbook = nil
		return err
	}
	playbook = nil
	return nil

}
func main()  {

	for ; ;  {
		ansible()
		time.Sleep(5 * time.Minute)
	}
}`

`
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root           1  0.0  0.0 1229188 6164 pts/0    Ssl+ 12:13   0:00 /app
root          17  0.0  0.0      0     0 ?        Zs   12:13   0:00 [ssh] <defunct>
root          18  0.0  0.0      0     0 ?        Zs   12:13   0:00 [ssh] <defunct>
root          64  0.0  0.0   7636  4192 pts/1    Ss   12:14   0:00 bash
root         102  0.0  0.0      0     0 ?        Zs   12:18   0:00 [ssh] <defunct>
root         103  0.0  0.0      0     0 ?        Zs   12:18   0:00 [ssh] <defunct>
root         393  0.0  0.0      0     0 ?        Zs   12:23   0:00 [ssh] <defunct>
root         394  0.0  0.0      0     0 ?        Zs   12:23   0:00 [ssh] <defunct>
root         437  0.0  0.0      0     0 ?        Zs   12:29   0:00 [ssh] <defunct>
root         438  0.0  0.0      0     0 ?        Zs   12:29   0:00 [ssh] <defunct>
root         481  0.0  0.0      0     0 ?        Zs   12:34   0:00 [ssh] <defunct>
root         482  0.0  0.0      0     0 ?        Zs   12:34   0:00 [ssh] <defunct>
root         525  0.0  0.0      0     0 ?        Zs   12:39   0:00 [ssh] <defunct>
root         526  0.0  0.0      0     0 ?        Zs   12:39   0:00 [ssh] <defunct>
root         569  0.0  0.0      0     0 ?        Zs   12:44   0:00 [ssh] <defunct>
root         570  0.0  0.0      0     0 ?        Zs   12:44   0:00 [ssh] <defunct>
root         613  0.0  0.0      0     0 ?        Zs   12:49   0:00 [ssh] <defunct>
root         614  0.0  0.0      0     0 ?        Zs   12:49   0:00 [ssh] <defunct>
root         657  0.0  0.0      0     0 ?        Zs   12:54   0:00 [ssh] <defunct>
root         658  0.0  0.0      0     0 ?        Zs   12:54   0:00 [ssh] <defunct>
root         701  0.0  0.0      0     0 ?        Zs   12:59   0:00 [ssh] <defunct>
root         702  0.0  0.0      0     0 ?        Zs   12:59   0:00 [ssh] <defunct>
root         745  0.0  0.0      0     0 ?        Zs   13:05   0:00 [ssh] <defunct>
root         746  0.0  0.0      0     0 ?        Zs   13:05   0:00 [ssh] <defunct>
root         789  0.0  0.0      0     0 ?        Zs   13:10   0:00 [ssh] <defunct>
root         790  0.0  0.0      0     0 ?        Zs   13:10   0:00 [ssh] <defunct>
root         833  0.0  0.0      0     0 ?        Zs   13:15   0:00 [ssh] <defunct>
root         834  0.0  0.0      0     0 ?        Zs   13:15   0:00 [ssh] <defunct>
root         877  0.0  0.0      0     0 ?        Zs   13:20   0:00 [ssh] <defunct>
root         878  0.0  0.0      0     0 ?        Zs   13:20   0:00 [ssh] <defunct>
root         921  0.0  0.0      0     0 ?        Zs   13:25   0:00 [ssh] <defunct>
root         922  0.0  0.0      0     0 ?        Zs   13:25   0:00 [ssh] <defunct>
root         965  0.0  0.0      0     0 ?        Zs   13:30   0:00 [ssh] <defunct>
root         966  0.0  0.0      0     0 ?        Zs   13:30   0:00 [ssh] <defunct>
root        1009  0.0  0.0      0     0 ?        Zs   13:35   0:00 [ssh] <defunct>
root        1010  0.0  0.0      0     0 ?        Zs   13:35   0:00 [ssh] <defunct>
root        1053  0.0  0.0      0     0 ?        Zs   13:41   0:00 [ssh] <defunct>
root        1054  0.0  0.0      0     0 ?        Zs   13:41   0:00 [ssh] <defunct>
root        1097  0.0  0.0      0     0 ?        Zs   13:46   0:00 [ssh] <defunct>
root        1098  0.0  0.0      0     0 ?        Zs   13:46   0:00 [ssh] <defunct>
root        1141  0.0  0.0      0     0 ?        Zs   13:51   0:00 [ssh] <defunct>
root        1142  0.0  0.0      0     0 ?        Zs   13:51   0:00 [ssh] <defunct>
root        1185  0.0  0.0      0     0 ?        Zs   13:56   0:00 [ssh] <defunct>
root        1186  0.0  0.0      0     0 ?        Zs   13:56   0:00 [ssh] <defunct>
root        1229  0.0  0.0      0     0 ?        Zs   14:01   0:00 [ssh] <defunct>
root        1230  0.0  0.0      0     0 ?        Zs   14:01   0:00 [ssh] <defunct>
root        1273  0.0  0.0      0     0 ?        Zs   14:06   0:00 [ssh] <defunct>
root        1274  0.0  0.0      0     0 ?        Zs   14:06   0:00 [ssh] <defunct>
root        1317  0.0  0.0      0     0 ?        Zs   14:11   0:00 [ssh] <defunct>
root        1318  0.0  0.0      0     0 ?        Zs   14:11   0:00 [ssh] <defunct>
root        1361  0.0  0.0      0     0 ?        Zs   14:16   0:00 [ssh] <defunct>
root        1362  0.1  0.0      0     0 ?        Zs   14:16   0:00 [ssh] <defunct>

`



Discussion:
@apenella: Hi @neodiz!
Thank you very much for raising that issue. I will take a look at this.
Just letting you know, I will work on fixing that over the v2.0.0. By the way, have you tried setting a cancel context instead of the TODO one?

I keep you updated!@apenella: Hi @neodiz!
I could reproduce what you experienced, which allowed me to work on a solution to that issue. Unfortunately, I don't have a fix for that yet.
My first thought was that the go-ansible code did not manage properly the processes spawned by the process started by the library. I tried to set up the [SysProcAttr](https://pkg.go.dev/syscall#SysProcAttr) or create a process group to handle the child processes but any of those was a deterministic solution.

I have realized that when I executed the ansible-playbook it also left some SSH zombie processes, and that allowed me to explore new avenues.
The cause of my problem was that I executed the playbook inside a container without the init enabled (read [here](https://github.com/ansible/ansible/issues/49270)), so no one reaped zombie processes.

Please, can you provide me with the following information?
- Are you running Ansible within a container?
- As is said [here](https://groups.google.com/g/ansible-project/c/IQoTNwDBIiA/m/qiHUTgg31lkJ), Ansible sets the SSH `-tt` parameter and that could leave zombie process on your system, coming from the pseudo-tty. That parameter should be overwritten by adding the `-T` on the SSH extra arguments. Can you confirm if you still have zombie processes after setting the `-T` parameter?

Thank you very much!
  @neodiz: Hi. Yes i use code on docker container.
About parameter i check. A very interesting situation. I recommend adding a
mention of this issue in the documentation. I work as a devops and custom
module developer and other developers will not be able to understand what
the problem is. This may affect your library's reputation even though it is
very good.

вт, 19 мар. 2024 г. в 10:06, apenella ***@***.***>:

> Hi @neodiz <https://github.com/neodiz>!
> I could reproduce what you experienced, which allowed me to work on a
> solution to that issue. Unfortunately, I don't have a fix for that yet.
> My first thought was that the go-ansible code did not manage properly the
> processes spawned by the process started by the library. I tried to set up
> the SysProcAttr <https://pkg.go.dev/syscall#SysProcAttr> or create a
> process group to handle the child processes but any of those was a
> deterministic solution.
>
> I have realized that when I executed the ansible-playbook it also left
> some SSH zombie processes, and that allowed me to explore new avenues.
> The cause of my problem was that I executed the playbook inside a
> container without the init enabled (read here
> <https://github.com/ansible/ansible/issues/49270>), so no one reaped
> zombie processes.
>
> Please, can you provide me with the following information?
>
>    - Are you running Ansible within a container?
>    - As is said here
>    <https://groups.google.com/g/ansible-project/c/IQoTNwDBIiA/m/qiHUTgg31lkJ>,
>    Ansible sets the SSH -tt parameter and that could leave zombie process
>    on your system, coming from the pseudo-tty. That parameter should be
>    overwritten by adding the -T on the SSH extra arguments. Can you
>    confirm if you still have zombie processes after setting the -T
>    parameter?
>
> Thank you very much!
>
> —
> Reply to this email directly, view it on GitHub
> <https://github.com/apenella/go-ansible/issues/139#issuecomment-2006022667>,
> or unsubscribe
> <https://github.com/notifications/unsubscribe-auth/ABRLCKT6ZSEJB2HPBMFYYXLYY7PYDAVCNFSM6AAAAABEABCTS2VHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDAMBWGAZDENRWG4>
> .
> You are receiving this because you were mentioned.Message ID:
> ***@***.***>
>
@apenella: Hi @neodiz!
I understand that the issue has been solved and I could close the issue.
I will follow your recommendation and I will include a section in the documentation explaining that behaviour and how to solve this.

Thank you very much!@apenella: Available in [v2.0.0-rc-3](https://github.com/apenella/go-ansible/releases/tag/v2.0.0-rc.3)@aravindhp: I am noticing another variant of this issue. The Ansible playbook uses jump hosts for connecting to the final host. It looks like when `delegate_to` is used with jump hosts, it creates a chain of SSH processes. The main Ansible process spawns SSH processes, which in turn spawn other SSH processes. These intermediary processes aren't cleaned up properly and become zombies. At least this is the conjecture on my part as even adding the `-T` parameter is not helping. Am I on the correct track? I am not sure how to handle this scenario. Any pointers will be appreciated.@apenella: Hi @aravindhp!
Let me try to reproduce this behaviour.

BTW, did you experienced this with Ansible playbook directly or only when you execute Ansible through `go-ansible`?

Thanks!@aravindhp: >Let me try to reproduce this behaviour.

Thank you!

>did you experienced this with Ansible playbook directly or only when you execute Ansible through go-ansible?

Only when I execute the playbook using go-ansible in a container. I am using the [ubi9-init](https://catalog.redhat.com/software/containers/ubi9-init/6183297540a2d8e95c82e8bd) image@aravindhp: I tried writing my own executor that tries to do a process group clean up once the context is canceled:
```go
func (e *ProcessAwareExecutor) Execute(ctx context.Context) error {
	if e.cmd == nil {
		return fmt.Errorf("no command to execute")
	}

	cmdStr, err := e.cmd.Command()
	if err != nil {
		return fmt.Errorf("error getting command: %w", err)
	}
	if len(cmdStr) == 0 {
		return fmt.Errorf("empty command")
	}

	cmd := exec.CommandContext(ctx, cmdStr[0], cmdStr[1:]...)

	cmd.Env = os.Environ()
	for name, value := range e.envVars {
		cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", name, value))
	}

	// Set up process group for proper cleanup
	cmd.SysProcAttr = &syscall.SysProcAttr{
		Setpgid: true, // Create a new process group
	}

	// Set up IO
	if e.write != nil && !e.quietStdout {
		cmd.Stdout = e.write
	}

	if e.writeError != nil {
		cmd.Stderr = e.writeError
	}

	// Start the command
	if err := cmd.Start(); err != nil {
		return fmt.Errorf("error starting command: %w", err)
	}

	// Get the process group ID for cleanup
	pgid := cmd.Process.Pid

	// Setup a goroutine to kill the process group when the context is done
	go func() {
		<-ctx.Done()
		// Kill all processes in the group on context cancellation
		killErr := syscall.Kill(-pgid, syscall.SIGTERM)
		slog.DebugContext(ctx, "Killing process group", "pgid", pgid, "error", killErr)
	}()

	// Wait for command completion
	err = cmd.Wait()

	// Ensure cleanup of process group, even if the command completed normally
	// This is critical for SSH processes with -W flag that might otherwise become zombies
	if cmd.Process != nil {
		killErr := syscall.Kill(-pgid, syscall.SIGTERM)
		slog.DebugContext(ctx, "Killing process group again", "pgid", pgid, "error", killErr)
	}

	if err != nil && e.errorEnricher != nil {
		return e.errorEnricher.Enrich(err)
	}

	return err
}
```
But that did not help. Not 100% sure of it though.@aravindhp: This turned out to be user error 🤦🏽 I had misconfigured my Dockerfile and there was actually no PID 1 init process. My application was running as the PID 1 process causing this issue. Apologies for the noise 🙏🏽 @apenella: Hi @aravindhp! I'm glad to hear you find out the cause of your issue 😄 

Solution patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c713d45..a4c0313 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,85 @@ All notable changes to this project will be documented in this file.
 
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
+## v2.0.0-rc.3
+
+Version 2.0.0 of *go-ansible* introduces several disruptive changes. Read the upgrade guide carefully before proceeding with the upgrade.
+
+### BREAKING CHANGES
+
+> **Note**
+> The latest major version of _go-ansible_, version _2.x_, introduced significant and breaking changes. If you are currently using a version prior to _2.x_, please refer to the [upgrade guide](https://github.com/apenella/go-ansible/blob/master/docs/upgrade_guide_to_2.x.md) for detailed information on how to migrate to version _2.x_.
+
+- The Go module name has been changed from `github.com/apenella/go-ansible` to `github.com/apenella/go-ansible/v2`. So, you need to update your import paths to use the new module name.
+- The relationship between the executor and `AnsiblePlaybookCmd` / `AnsibleAdhocCmd` / `AnsibleInvetoryCmd` has undergone important changes.
+  - **Inversion of responsibilities**: The executor is now responsible for executing external commands, while `AnsiblePlaybookCmd`, `AnsibleInventoryCmd` and `AnsibleAdhocCmd` have cut down their responsibilities, primarily focusing on generating the command to be executed.
+  - **Method and Attribute Removal**: The following methods and attributes have been removed on `AnsiblePlaybookCmd`, `AnsibleInventoryCmd` and `AnsibleAdhocCmd`:
+    - The `Run` method.
+    - The `Exec` and `StdoutCallback` attributes.
+  - **Attributes Renaming**: The `Options` attribute has been renamed to `PlaybookOptions` in `AnsiblePlaybookCmd`, `AdhocOptions` in `AnsibleAdhocCmd` and `InventoryOptions` in `AnsibleInventoryCmd`.
+- The `Executor` interface has undergone a significant signature change. This change entails the removal of the following arguments `resultsFunc` and `options`. The current signature is: `Execute(ctx context.Context) error`.
+- The `github.com/apenella/go-ansible/pkg/options` package has been removed. After that deletion, the attributes from `AnsibleConnectionOptions` and `AnsiblePrivilegeEscalationOptions` attributes have been moved to the `PlaybookOptions`, `AdhocOptions` and `InventoryOptions` structs.
+- The `github.com/apenella/go-ansible/pkg/stdoutcallback` package has been removed. Its responsibilities have been absorbed by two distinc packages `github.com/apenella/go-ansible/v2/pkg/execute/result`, which manages the output of the commands, and `github.com/apenella/go-ansible/v2/pkg/execute/stdoutcallback` that enables the setting of the stdout callback.
+- The constants `AnsibleForceColorEnv` and `AnsibleHostKeyCheckingEnv` have been removed from the `github.com/apenella/go-ansible/pkg/options` package.
+- The functions `AnsibleForceColor`, `AnsibleAvoidHostKeyChecking` and `AnsibleSetEnv` have been removed from the `github.com/apenella/go-ansible/pkg/options` package. Use the `ExecutorWithAnsibleConfigurationSettings` decorator instead defined in the `github.com/apenella/go-ansible/v2/pkg/execute/configuration` package.
+- The methods `WithWrite` and `WithShowduration` have been removed from the `ExecutorTimeMeasurement` decorator. Instead, a new method named `Duration` has been introduced for obtaining the duration of the execution.
+
+### Fixed
+
+- Quote properly the attributes `SCPExtraArgs`, `SFTPExtraArgs`, `SSHCommonArgs`, `SSHExtraArgs` in `AnsibleAdhocOptions` and `AnsiblePlaybookOptions` structs when generating the command to be executed. #140
+
+### Added
+
+- `AnsibleAdhocExecute` _executor_ has been introduced. That _executor_ allows you to create an executor to run `ansible` commands using the default settings of `DefaultExecute`. This _executor_ is located in the `github.com/apenella/go-ansible/v2/pkg/execute/adhoc` package.
+- `AnsibleInventoryExecute` _executor_ has been introduced. That _executor_ allows you to create an executor to run `ansible-inventory` commands using the default settings of `DefaultExecute`. This _executor_ is located in the `github.com/apenella/go-ansible/v2/pkg/execute/inventory` package.
+- `ansibleplaybook-extravars` example to show how to configure extra vars when running an Ansible playbook command.
+- `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.
+- `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.
+- `ExecutorStdoutCallbackSetter` 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 for Ansible executions.
+- `github.com/apenella/go-ansible/v2/internal/executable/os/exec` package has been introduced. This package serves as a wrapper for `os.exec`.
+- `github.com/apenella/go-ansible/v2/pkg/execute/configuration`  package includes the `ExecutorWithAnsibleConfigurationSettings` struct, which acts as a decorator that facilitates the configuration of Ansible settings within the executor.
+- `github.com/apenella/go-ansible/v2/pkg/execute/result/default` package has been introduced. This package offers the default component for printing execution results. It supersedes the `DefaultStdoutCallbackResults` function that was previously defined in the `github.com/apenella/go-ansible/v2/pkg/stdoutcallback` package.
+- `github.com/apenella/go-ansible/v2/pkg/execute/result/json` package has been introduced. This package offers the component for printing execution results from the JSON stdout callback. It supersedes the `JSONStdoutCallbackResults` function that was previously defined in the `github.com/apenella/go-ansible/v2/pkg/stdoutcallback` package.
+- `github.com/apenella/go-ansible/v2/pkg/execute/stdoutcallback`. package has been introduced and offers multiple decorators designed to set the stdout callback for Ansible executions.
+- `github.com/apenella/go-ansible/v2/pkg/execute/workflow` package has been introduced and allows you to define a workflow for executing multiple commands in a sequence.
+- `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.
+- `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`.
+
+### Changed
+
+- The `AnsibleAdhocCmd` struct has been updated to implement the `Commander` interface.
+- The `AnsibleInventoryCmd` struct has been updated to implement the `Commander` interface.
+- The `AnsiblePlaybookCmd` struct has been updated to implement the `Commander` interface.
+- The `AnsiblePlaybookOptions` and `AnsibleAdhocOptions` structs have been updated to include the attributes from `AnsibleConnectionOptions` and `AnsiblePrivilegeEscalationOptions`.
+- The `DefaultExecute` struct has been updated to have a new attribute named `Exec` of type `Executabler` that is responsible for executing external commands.
+- The `DefaultExecute` struct has been updated to have a new attribute named `Output` of type `ResultsOutputer` that is responsible for printing the execution's output.
+- The `DefaultExecute` struct has been updated to implement the `Executor` interface.
+- The `DefaultExecute` struct has been updated to implement the `ExecutorEnvVarSetter` interface.
+- The `DefaultExecute` struct has been updated to implement the `ExecutorStdoutCallbackSetter` interface.
+- The `Options` attribute in `AnsibleAdhocCmd` struct has been renamed to `AdhocOptions`.
+- The `Options` attribute in `AnsibleInventoryCmd` struct has been renamed to `InventoryOptions`.
+- The `Options` attribute in `AnsiblePlaybookCmd` struct has been renamed to `PlaybookOptions`.
+- The examples has been adapted to use executor as the component to execute Ansible commands.
+- The package `github.com/apenella/go-ansible/pkg/stdoutcallback/result/transformer` has been moved to `github.com/apenella/go-ansible/v2/pkg/execute/result/transformer`.
+
+### Removed
+
+- The `Exec` attribute has been removed from `AnsiblePlaybookCmd` and `AdhocPlaybookCmd`.
+- The `github.com/apenella/go-ansible/pkg/options` package has been removed. After the `AnsibleConnectionOptions` and `AnsiblePrivilegeEscalationOptions` structs are not available anymore.
+- The `github.com/apenella/go-ansible/pkg/stdoutcallback` package has been removed.
+- The `Run` method has been removed from the `AnsiblePlaybookCmd` and `AdhocPlaybookCmd` structs.
+- The `ShowDuration` attribute in the `DefaultExecute` struct has been removed.
+- The `StdoutCallback` attribute has been removed from `AnsiblePlaybookCmd` and `AdhocPlaybookCmd`.
+- The constants `AnsibleForceColorEnv` and `AnsibleHostKeyCheckingEnv` have been removed from the `github.com/apenella/go-ansible/pkg/options` package.
+- The functions `AnsibleForceColor`, `AnsibleAvoidHostKeyChecking` and `AnsibleSetEnv` have been removed from the `github.com/apenella/go-ansible/pkg/options` package. Use the `ExecutorWithAnsibleConfigurationSettings` decorator instead defined in the `github.com/apenella/go-ansible/v2/pkg/execute/configuration` package.
+- The methods `WithWrite` and `withshowduration` have been removed from the `ExecutorTimeMeasurement` decorator.
+
 ## v2.0.0-rc.2
 
 Version 2.0.0 of *go-ansible* introduces several disruptive changes. Read the upgrade guide carefully before proceeding with the upgrade.
diff --git a/README.md b/README.md
index 4c29249..eaf31ff 100644
--- a/README.md
+++ b/README.md
@@ -23,6 +23,9 @@ _**Important:** The master branch may contain unreleased or pre-released feature
     - [Executor](#executor)
     - [Command Generator](#command-generator)
     - [Results Handler](#results-handler)
+  - [Considerations](#considerations)
+    - [Execute go-ansible inside a container](#execute-go-ansible-inside-a-container)
+    - [Disable pseudo-terminal allocation](#disable-pseudo-terminal-allocation)
   - [Getting Started](#getting-started)
     - [Create the _AnsiblePlaybookCmd_ struct](#create-the-ansibleplaybookcmd-struct)
     - [Create the _DefaultExecute_ executor](#create-the-defaultexecute-executor)
@@ -54,6 +57,13 @@ _**Important:** The master branch may contain unreleased or pre-released feature
           - [Stdout Callback Execute structs](#stdout-callback-execute-structs)
         - [Workflow package](#workflow-package)
           - [WorkflowExecute struct](#workflowexecute-struct)
+    - [Galaxy package](#galaxy-package)
+      - [Galaxy Collection Install package](#galaxy-collection-install-package)
+        - [AnsibleGalaxyCollectionInstallCmd struct](#ansiblegalaxycollectioninstallcmd-struct)
+        - [AnsibleGalaxyCollectionInstallOptions struct](#ansiblegalaxycollectioninstalloptions-struct)
+      - [Galaxy Role Install package](#galaxy-role-install-package)
+        - [AnsibleGalaxyRoleInstallCmd struct](#ansiblegalaxyroleinstallcmd-struct)
+        - [AnsibleGalaxyRoleInstallOptions struct](#ansiblegalaxyroleinstalloptions-struct)
     - [Inventory package](#inventory-package)
       - [AnsibleInventoryCmd struct](#ansibleinventorycmd-struct)
       - [AnsibleInventoryExecute struct](#ansibleinventoryexecute-struct)
@@ -79,7 +89,7 @@ _**Important:** The master branch may contain unreleased or pre-released feature
 Use this command to fetch and install the _go-ansible_ module. You can install the release candidate version by executing the following command:
 
 ```sh
-go get github.com/apenella/go-ansible/v2@v2.0.0-rc.2
+go get github.com/apenella/go-ansible/v2@v2.0.0-rc.3
 ```
 
 You can also install the latest stable version by executing the following command:
@@ -111,7 +121,23 @@ A _command generator_ or a _commander_ is responsible for generating the command
 
 ### Results Handler
 
-A _results handler_ or an _results outputer_ is responsible for managing the output of the command execution. The library includes two output mechanisms: the [DefaultResults](#defaultexecute-struct) and the [JSONStdoutCallbackResults](#jsonstdoutcallbackresults-struct) structs.
+A _results handler_ or a _results outputer_ is responsible for managing the output of the command execution. The library includes two output mechanisms: the [DefaultResults](#defaultresults-struct) and the [JSONStdoutCallbackResults](#jsonstdoutcallbackresults-struct) structs.
+
+## Considerations
+
+Before you proceed further, please take note of the following considerations to ensure optimal usage of the go-ansible library.
+
+### Execute go-ansible inside a container
+
+When executing _Ansible_ commands using the _go-ansible_ library inside a container, ensure that the container has configured an init system. The init system is necessary to manage the child processes created by the _Ansible_ commands. If the container does not have an init system, the child processes may not be correctly managed, leading to unexpected behavior such as zombie processes.
+
+You can read more about that in the issue [139](https://github.com/apenella/go-ansible/issues/139) and [here](https://github.com/ansible/ansible/issues/49270#issuecomment-462306244).
+
+### Disable pseudo-terminal allocation
+
+_Ansible_ commands forces the pseudo-terminal allocation when executed in a terminal. That configuration can cause that the SSH connection leaves zombie processes when the command finished. If you are experiencing this issue, you can disable the pseudo-terminal allocation by setting the `-T` to the SSH extra arguments, that will disable the pseudo-terminal allocation.
+
+You can read more about that in the issue [139](https://github.com/apenella/go-ansible/issues/139) and [here](https://groups.google.com/g/ansible-project/c/IQoTNwDBIiA/m/qiHUTgg31lkJ).
 
 ## Getting Started
 
@@ -122,7 +148,7 @@ This section will guide you through the step-by-step process of using the _go-an
 
 Before proceeding, ensure you have installed the latest version of the _go-ansible_ library. If not, please refer to the [Installation section](#install) for instructions.
 
-To create an application that launches the `ansible-playbook` command you need to create an [AnsiblePlaybookCmd](#ansibleplaybookcmd-struct) struct. This struct generates the _Ansible_ command to be run. Then, you need to execute the command usingng an [executor](#executor). In that guided example, you will use the [DefaultExecute](#defaultexecute-struct) executor, an _executor_ provided by the _go-ansible_ library.
+To create an application that launches the `ansible-playbook` command you need to create an [AnsiblePlaybookCmd](#ansibleplaybookcmd-struct) struct. This struct generates the _Ansible_ command to be run. Then, you need to execute the command using an [executor](#executor)](#executor). In that guided example, you will use the [DefaultExecute](#defaultexecute-struct) executor, an _executor_ provided by the _go-ansible_ library.
 
 ### Create the _AnsiblePlaybookCmd_ struct
 
@@ -141,10 +167,11 @@ ansiblePlaybookOptions := &playbook.AnsiblePlaybookOptions{
 Finally, create the [AnsiblePlaybookCmd](#ansibleplaybookcmd-struct) struct that generates the command to execute the playbook `site.yml` using the `ansible-playbook` command:
 
 ```go
-playbookCmd := &playbook.AnsiblePlaybookCmd{
-  Playbook:         "site.yml",
-  PlaybookOptions:  ansiblePlaybookOptions,
-}
+playbookCmd := playbook.NewAnsiblePlaybookCmd(
+  playbook.WithPlaybooks("site.yml", "site2.yml"),
+  playbook.WithPlaybookOptions(ansiblePlaybookOptions),
+)
+
 ```
 
 Once the `AnsiblePlaybookCmd` is defined, provide the command to an [executor](#executor) to run the command.
@@ -206,6 +233,12 @@ The `github.com/apenella/go-ansible/v2/pkg/adhoc` package facilitates the genera
 
 The `AnsibleAdhocCmd` struct enables the generation of _Ansible ad-hoc_ commands. It implements the [Commander](#commander-interface) interface, so its method `Command` returns an array of strings that represents the command to be executed. An executor can use it to create the command to be executed.
 
+The package provides the `NewAnsibleAdhocCmd` function to create a new instance of the `AnsibleAdhocCmd` struct. The function accepts a list of options to customize the ad-hoc command. The following functions are available:
+
+- `WithAdhocOptions(options *AnsibleAdhocOptions) AdhocOptionsFunc`: Set the ad-hoc options for the command.
+- `WithBinary(binary string) AdhocOptionsFunc`: Set the binary for the ad-hoc command.
+- `WithPattern(pattern string) AdhocOptionsFunc`: Set the pattern for the ad-hoc command.
+
 The following code snippet demonstrates how to use the `AnsibleAdhocCmd` struct to generate an ad-hoc command:
 
 ```go
@@ -218,10 +251,10 @@ ansibleAdhocOptions := &adhoc.AnsibleAdhocOptions{
   }
 }
 
-adhocCmd := &adhoc.AnsibleAdhocCmd{
-  Pattern:  "all",
-  Options:  ansibleAdhocOptions,
-}
+adhocCmd := adhoc.NewAnsibleAdhocCmd(
+  adhoc.WithPattern("all"),
+  adhoc.WithAdhocOptions(ansibleAdhocOptions),
+)
 
 // Generate the command to be executed
 cmd, err := adhocCmd.Command()
@@ -320,10 +353,10 @@ The snippet below shows how to customize the `DefaultExecute` executor using the
 
 ```go
 // PlaybookCmd is the Commander responsible for generating the command to execute
-playbookCmd := &playbook.AnsiblePlaybookCmd{
-  Playbook:          "site.yml",
-  PlaygookOptions:   ansiblePlaybookOptions,
-}
+playbookCmd := playbook.NewAnsiblePlaybookCmd(
+  playbook.WithPlaybooks("site.yml"),
+  playbook.WithPlaybookOptions(ansiblePlaybookOptions),
+)
 
 // MyExecutabler is an hypothetical implementation of the Executabler interface
 executabler := &myExecutabler{}
@@ -368,10 +401,10 @@ The next code snippet demonstrates how to execute the `ansible-playbook` command
 
 ```go
 // Define the command to execute
-playbookCmd := &ansibler.AnsiblePlaybookCmd{
-  Playbook:          []string{"site.yml"},
-  PlaybookOptions:   ansiblePlaybookOptions,
-}
+playbookCmd := playbook.NewAnsiblePlaybookCmd(
+  playbook.WithPlaybooks("site.yml"),
+  playbook.WithPlaybookOptions(ansiblePlaybookOptions),
+)
 
 // Define an instance for the new executor and set the options
 exec := &MyExecutor{
@@ -427,10 +460,10 @@ ansiblePlaybookOptions := &playbook.AnsiblePlaybookOptions{
   Inventory:  "127.0.0.1,",
 }
 
-playbookCmd := &playbook.AnsiblePlaybookCmd{
-  Playbooks:        []string{"site.yml"},
-  PlaybookOptions:  ansiblePlaybookOptions,
-}
+playbookCmd := playbook.NewAnsiblePlaybookCmd(
+  playbook.WithPlaybooks("site.yml"),
+  playbook.WithPlaybookOptions(ansiblePlaybookOptions),
+)
 
 exec := configuration.NewAnsibleWithConfigurationSettingsExecute(
   execute.NewDefaultExecute(
@@ -461,10 +494,10 @@ ansiblePlaybookOptions := &playbook.AnsiblePlaybookOptions{
   Inventory:  "127.0.0.1,",
 }
 
-playbookCmd := &playbook.AnsiblePlaybookCmd{
-  Playbooks:         []string{"site.yml"},
-  PlaybookOptions:   ansiblePlaybookOptions,
-}
+playbookCmd := playbook.NewAnsiblePlaybookCmd(
+  playbook.WithPlaybooks("site.yml"),
+  playbook.WithPlaybookOptions(ansiblePlaybookOptions),
+)
 
 executorTimeMeasurement := measure.NewExecutorTimeMeasurement(
   execute.NewDefaultExecute(
@@ -685,6 +718,49 @@ if err != nil {
 }
 ```
 
+### Galaxy package
+
+The `go-ansible` library provides you with the ability to interact with the _Ansible Galaxy_ command-line tool. To do that it includes the following package:
+
+- [github.com/apenella/go-ansible/v2/pkg/galaxy/collection/install](#galaxy-collection-install-package): Provides the functionality to install collections from the _Ansible Galaxy_.
+- [github.com/apenella/go-ansible/v2/pkg/galaxy/role/install](#galaxy-role-install-package): Provides the functionality to install roles from the _Ansible Galaxy_.
+
+#### Galaxy Collection Install package
+
+The `github.com/apenella/go-ansible/v2/pkg/galaxy/collection/install` package allows you to install collections from the _Ansible Galaxy_ using the `ansible-galaxy` command. The package provides the following structs and functions:
+
+##### AnsibleGalaxyCollectionInstallCmd struct
+
+The `AnsibleGalaxyCollectionInstallCmd` struct enables the generation of `ansible-galaxy` commands to install collections. It implements the [Commander](#commander-interface) interface, so its method `Command` returns an array of strings that represents the command to be executed. An executor can use it to create the command to be executed.
+
+The package provides the `NewAnsibleGalaxyCollectionInstallCmd` function to create a new instance of the `AnsibleGalaxyCollectionInstallCmd` struct. The function accepts a list of options to customize the `ansible-galaxy` command. The following functions are available:
+
+- `WithBinary(binary string) AnsibleGalaxyCollectionInstallOptionsFunc`: Set the binary for the `ansible-galaxy` command.
+- `WithCollectionInstallOptions(options *AnsibleGalaxyCollectionInstallOptions) AnsibleGalaxyCollectionInstallOptionsFunc`: Set the collection install options for the command.
+- `WithCollectionNames(collectionNames ...string) AnsibleGalaxyCollectionInstallOptionsFunc`: Set the collection names for the `ansible-galaxy` command.
+
+##### AnsibleGalaxyCollectionInstallOptions struct
+
+The `AnsibleGalaxyCollectionInstallOptions` struct includes parameters described in the `Options` section of the _Ansible Galaxy_ manual page. It defines the behavior of the _Ansible Galaxy_ collection installation operations and specifies where to find the configuration settings.
+
+#### Galaxy Role Install package
+
+The `github.com/apenella/go-ansible/v2/pkg/galaxy/role/install` package allows you to install roles from the _Ansible Galaxy_ using the `ansible-galaxy` command. The package provides the following structs and functions:
+
+##### AnsibleGalaxyRoleInstallCmd struct
+
+The `AnsibleGalaxyRoleInstallCmd` struct enables the generation of `ansible-galaxy` commands to install roles. It implements the [Commander](#commander-interface) interface, so its method `Command` returns an array of strings that represents the command to be executed. An executor can use it to create the command to be executed.
+
+The package provides the `NewAnsibleGalaxyRoleInstallCmd` function to create a new instance of the `AnsibleGalaxyRoleInstallCmd` struct. The function accepts a list of options to customize the `ansible-galaxy` command. The following functions are available:
+
+- `WithBinary(binary string) AnsibleGalaxyRoleInstallOptionsFunc`: Set the binary for the `ansible-galaxy` command.
+- `WithGalaxyRoleInstallOptions(options *AnsibleGalaxyRoleInstallOptions) AnsibleGalaxyRoleInstallOptionsFunc`: Set the role install options for the command.
+- `WithRoleNames(roleNames ...string) AnsibleGalaxyRoleInstallOptionsFunc`: Set the role names for the `ansible-galaxy` command.
+
+##### AnsibleGalaxyRoleInstallOptions struct
+
+The `AnsibleGalaxyRoleInstallOptions` struct includes parameters described in the `Options` section of the _Ansible Galaxy_ manual page. It defines the behavior of the _Ansible Galaxy_ role installation operations and specifies where to find the configuration settings.
+
 ### Inventory package
 
 The information provided in this section gives an overview of the `Inventory` package in `go-ansible`.
@@ -695,6 +771,12 @@ The `github.com/apenella/go-ansible/v2/pkg/inventory` package provides the funct
 
 The `AnsibleInventoryCmd` struct enables the generation of `ansible-inventory` commands. It implements the [Commander](#commander-interface) interface, so its method `Command` returns an array of strings that represents the command to be executed. An executor can use it to create the command to be executed.
 
+The package provides the `NewAnsibleInventoryCmd` function to create a new instance of the `AnsibleInventoryCmd` struct. The function accepts a list of options to customize the `ansible-inventory` command. The following functions are available:
+
+- `WithBinary(binary string) InventoryOptionsFunc`: Set the binary for the `ansible-inventory` command.
+- `WithInventoryOptions(options *AnsibleInventoryOptions) InventoryOptionsFunc`: Set the inventory options for the command.
+- `WithPattern(pattern string) InventoryOptionsFunc`: Set the pattern for the `ansible-inventory` command.
+
 > Note
 > Unlike other _Ansible_ commands, the `ansible-inventory` command does not provide privilege escalation or connection options, aligning with the functionality of the command itself.
 
@@ -735,6 +817,12 @@ The `github.com/apenella/go-ansible/v2/pkg/playbook` package facilitates the gen
 
 The `AnsiblePlaybookCmd` struct enables the generation of _ansible-playbook_ commands. It implements the [Commander](#commander-interface) interface, so its method `Command` returns an array of strings that represents the command to be executed. An executor can use it to create the command to be executed.
 
+The package provides the `NewAnsiblePlaybookCmd` function to create a new instance of the `AnsiblePlaybookCmd` struct. The function accepts a list of options to customize the _ansible-playbook_ command. The following functions are available:
+
+- `WithBinary(binary string) PlaybookOptionsFunc`: Set the binary for the _ansible-playbook_ command.
+- `WithPlaybookOptions(options *AnsiblePlaybookOptions) PlaybookOptionsFunc`: Set the playbook options for the command.
+- `WithPlaybooks(playbooks ...string) PlaybookOptionsFunc`: Set the playbooks for the _ansible-playbook_ command.
+
 Next is an example of how to use the `AnsiblePlaybookCmd` struct to generate an _ansible-playbook_ command:
 
 ```go
@@ -744,10 +832,10 @@ ansiblePlaybookOptions := &playbook.AnsiblePlaybookOptions{
   Inventory:  "127.0.0.1,",
 }
 
-playbookCmd := &playbook.AnsiblePlaybookCmd{
-  Playbook:         "site.yml",
-  PlaybookOptions:  ansiblePlaybookOptions,
-}
+playbookCmd := playbook.NewAnsiblePlaybookCmd(
+  playbook.WithPlaybooks("site.yml"),
+  playbook.WithPlaybookOptions(ansiblePlaybookOptions),
+)
 
 // Generate the command to be executed
 cmd, err := playbookCmd.Command()
@@ -922,6 +1010,7 @@ Here you have a list of examples:
 - [ansibleplaybook-cobra-cmd](https://github.com/apenella/go-ansible/tree/master/examples/ansibleplaybook-cobra-cmd)
 - [ansibleplaybook-custom-transformer](https://github.com/apenella/go-ansible/tree/master/examples/ansibleplaybook-custom-transformer)
 - [ansibleplaybook-extravars-file](https://github.com/apenella/go-ansible/tree/master/examples/ansibleplaybook-extravars-file)
+- [ansibleplaybook-extravars](https://github.com/apenella/go-ansible/tree/master/examples/ansibleplaybook-extravars)
 - [ansibleplaybook-json-stdout](https://github.com/apenella/go-ansible/tree/master/examples/ansibleplaybook-json-stdout)
 - [ansibleplaybook-myexecutor](https://github.com/apenella/go-ansible/tree/master/examples/ansibleplaybook-myexecutor)
 - [ansibleplaybook-signals-and-cancellation](https://github.com/apenella/go-ansible/tree/master/examples/ansibleplaybook-signals-and-cancellation)
@@ -929,6 +1018,7 @@ Here you have a list of examples:
 - [ansibleplaybook-simple-with-prompt](https://github.com/apenella/go-ansible/tree/master/examples/ansibleplaybook-simple-with-prompt)
 - [ansibleplaybook-simple](https://github.com/apenella/go-ansible/tree/master/examples/ansibleplaybook-simple)
 - [ansibleplaybook-skipping-failing](https://github.com/apenella/go-ansible/tree/master/examples/ansibleplaybook-skipping-failing)
+- [ansibleplaybook-ssh](https://github.com/apenella/go-ansible/tree/master/examples/ansibleplaybook-ssh)
 - [ansibleplaybook-time-measurement](https://github.com/apenella/go-ansible/tree/master/examples/ansibleplaybook-time-measurement)
 - [ansibleplaybook-walk-through-json-output](https://github.com/apenella/go-ansible/tree/master/examples/ansibleplaybook-walk-through-json-output)
 - [ansibleplaybook-with-executor-time-measurament](https://github.com/apenella/go-ansible/tree/master/examples/ansibleplaybook-with-executor-time-measurament)
diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md
index c1abaa8..f33345b 100644
--- a/RELEASE_NOTES.md
+++ b/RELEASE_NOTES.md
@@ -1,6 +1,6 @@
 # Release notes
 
-## v2.0.0-rc.2
+## v2.0.0-rc.3
 
 Version 2.0.0 of *go-ansible* introduces several disruptive changes. Read the upgrade guide carefully before proceeding with the upgrade.
 
@@ -11,7 +11,7 @@ Version 2.0.0 of *go-ansible* introduces several disruptive changes. Read the up
 
 - The Go module name has been changed from `github.com/apenella/go-ansible` to `github.com/apenella/go-ansible/v2`. So, you need to update your import paths to use the new module name.
 - The relationship between the executor and `AnsiblePlaybookCmd` / `AnsibleAdhocCmd` / `AnsibleInvetoryCmd` has undergone important changes.
-  - **Inversion of responsabilities**: The executor is now responsible for executing external commands, while `AnsiblePlaybookCmd`, `AnsibleInventoryCmd` and `AnsibleAdhocCmd` have cut down their responsibilities, primarily focusing on generating the command to be executed.
+  - **Inversion of responsibilities**: The executor is now responsible for executing external commands, while `AnsiblePlaybookCmd`, `AnsibleInventoryCmd` and `AnsibleAdhocCmd` have cut down their responsibilities, primarily focusing on generating the command to be executed.
   - **Method and Attribute Removal**: The following methods and attributes have been removed on `AnsiblePlaybookCmd`, `AnsibleInventoryCmd` and `AnsibleAdhocCmd`:
     - The `Run` method.
     - The `Exec` and `StdoutCallback` attributes.
@@ -23,23 +23,32 @@ Version 2.0.0 of *go-ansible* introduces several disruptive changes. Read the up
 - The functions `AnsibleForceColor`, `AnsibleAvoidHostKeyChecking` and `AnsibleSetEnv` have been removed from the `github.com/apenella/go-ansible/pkg/options` package. Use the `ExecutorWithAnsibleConfigurationSettings` decorator instead defined in the `github.com/apenella/go-ansible/v2/pkg/execute/configuration` package.
 - The methods `WithWrite` and `WithShowduration` have been removed from the `ExecutorTimeMeasurement` decorator. Instead, a new method named `Duration` has been introduced for obtaining the duration of the execution.
 
+### Fixed
+
+- Quote properly the attributes `SCPExtraArgs`, `SFTPExtraArgs`, `SSHCommonArgs`, `SSHExtraArgs` in `AnsibleAdhocOptions` and `AnsiblePlaybookOptions` structs when generating the command to be executed. #140
+
 ### Added
 
-- A new _executor_ `AnsibleAdhocExecute` has been introduced. That _executor_ allows you to create an executor to run `ansible` commands using the default settings of `DefaultExecute`. This _executor_ is located in the `github.com/apenella/go-ansible/v2/pkg/execute/adhoc` package.
-- A new _executor_ `AnsibleInventoryExecute` has been introduced. That _executor_ allows you to create an executor to run `ansible-inventory` commands using the default settings of `DefaultExecute`. This _executor_ is located in the `github.com/apenella/go-ansible/v2/pkg/execute/inventory` package.
-- A new _executor_ `AnsiblePlaybookExecute` 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.
-- A new interface `Commander` 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.
-- A new interface `Executabler` 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.
-- A new interface `ExecutorEnvVarSetter` in `github.com/apenella/go-ansible/v2/pkg/execute/configuration` that defines the criteria for a struct to be compliant in setting Ansible configuration.
-- A new interface `ExecutorStdoutCallbackSetter` 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 for Ansible executions.
-- A new interface named `ResultsOutputer` has been introduced in the `github.com/apenella/go-ansible/v2/pkg/execute/result` pacakge.  This interface defines the criteria for a struct to be compliant in printing execution results.
-- A new package `github.com/apenella/go-ansible/v2/internal/executable/os/exec` has been introduced. This package serves as a wrapper for `os.exec`.
-- A new package `github.com/apenella/go-ansible/v2/pkg/execute/configuration` that incldues the `ExecutorWithAnsibleConfigurationSettings` struct, which acts as a decorator that facilitates the configuration of Ansible settings within the executor.
-- A new package `github.com/apenella/go-ansible/v2/pkg/execute/result/default` has been introduced. This package offers the default component for printing execution results. It supersedes the `DefaultStdoutCallbackResults` function that was previously defined in the `github.com/apenella/go-ansible/v2/pkg/stdoutcallback` package.
-- A new package `github.com/apenella/go-ansible/v2/pkg/execute/result/json` has been introduced. This package offers the component for printing execution results from the JSON stdout callback. It supersedes the `JSONStdoutCallbackResults` function that was previously defined in the `github.com/apenella/go-ansible/v2/pkg/stdoutcallback` package.
-- A new package `github.com/apenella/go-ansible/v2/pkg/execute/stdoutcallback`. This package offers multiple decorators designed to set the stdout callback for Ansible executions.
-- A new package `github.com/apenella/go-ansible/v2/pkg/execute/workflow` has been introduced. This package allows you to define a workflow for executing multiple commands in a sequence.
-- An utility to generate the code for the configuration package has been introduced. This utility is located in the `utils/cmd/configGenerator.go`.
+- `AnsibleAdhocExecute` _executor_ has been introduced. That _executor_ allows you to create an executor to run `ansible` commands using the default settings of `DefaultExecute`. This _executor_ is located in the `github.com/apenella/go-ansible/v2/pkg/execute/adhoc` package.
+- `AnsibleInventoryExecute` _executor_ has been introduced. That _executor_ allows you to create an executor to run `ansible-inventory` commands using the default settings of `DefaultExecute`. This _executor_ is located in the `github.com/apenella/go-ansible/v2/pkg/execute/inventory` package.
+- `ansibleplaybook-extravars` example to show how to configure extra vars when running an Ansible playbook command.
+- `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.
+- `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.
+- `ExecutorStdoutCallbackSetter` 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 for Ansible executions.
+- `github.com/apenella/go-ansible/v2/internal/executable/os/exec` package has been introduced. This package serves as a wrapper for `os.exec`.
+- `github.com/apenella/go-ansible/v2/pkg/execute/configuration`  package includes the `ExecutorWithAnsibleConfigurationSettings` struct, which acts as a decorator that facilitates the configuration of Ansible settings within the executor.
+- `github.com/apenella/go-ansible/v2/pkg/execute/result/default` package has been introduced. This package offers the default component for printing execution results. It supersedes the `DefaultStdoutCallbackResults` function that was previously defined in the `github.com/apenella/go-ansible/v2/pkg/stdoutcallback` package.
+- `github.com/apenella/go-ansible/v2/pkg/execute/result/json` package has been introduced. This package offers the component for printing execution results from the JSON stdout callback. It supersedes the `JSONStdoutCallbackResults` function that was previously defined in the `github.com/apenella/go-ansible/v2/pkg/stdoutcallback` package.
+- `github.com/apenella/go-ansible/v2/pkg/execute/stdoutcallback`. package has been introduced and offers multiple decorators designed to set the stdout callback for Ansible executions.
+- `github.com/apenella/go-ansible/v2/pkg/execute/workflow` package has been introduced and allows you to define a workflow for executing multiple commands in a sequence.
+- `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.
+- `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`.
 
 ### Changed
 
diff --git a/docs/upgrade_guide_to_2.x.md b/docs/upgrade_guide_to_2.x.md
index 2d3c69c..e2ceb4f 100644
--- a/docs/upgrade_guide_to_2.x.md
+++ b/docs/upgrade_guide_to_2.x.md
@@ -1,5 +1,15 @@
 # Upgrade Guide to go-ansible 2.x
 
+## Overview
+
+This document offers guidance for upgrading from _go-ansible_ _v1.x_ to _v2.x_. It also presents the changes introduced in _go-ansible v2.0.0_ since the major version _1.x_. Some of these are breaking changes.
+
+The most relevant change is that the package name has been changed from `github.com/apenella/go-ansible` to `github.com/apenella/go-ansible/v2`. So, you need to update your import paths to use the new module name.
+Another important change to highlight is that command structs no longer execute commands. So, `AnsiblePlaybookCmd` and `AnsibleAdhocCmd` do not require an `Executor` anymore. Instead, the `Executor` is responsible for the command execution. To achieve that, the `Executor` depends on the command structs to generate the commands to execute.
+That change is motivated by the need to segregate the command generation from the command execution. Having the `Executor` as the central component of the command execution process allows the `Executor` to be more flexible and customizable. The _go-ansible_ library provides a set of decorator structs to configure the `Executor` with different features, such as stdout callback management, and Ansible configuration settings.
+
+Proceed through the following sections to understand the changes in version _2.x_ and learn how to adapt your code accordingly.
+
 - [Upgrade Guide to go-ansible 2.x](#upgrade-guide-to-go-ansible-2x)
   - [Overview](#overview)
   - [Changes on the interfaces](#changes-on-the-interfaces)
@@ -37,23 +47,13 @@
       - [Replacing the _AnsibleAvoidHostKeyChecking_ function](#replacing-the-ansibleavoidhostkeychecking-function)
       - [Replacing the _AnsibleSetEnv_ function](#replacing-the-ansiblesetenv-function)
 
-## Overview
-
-This document offers guidance for upgrading from _go-ansible_ _v1.x_ to _v2.x_. It also presents the changes introduced in _go-ansible v2.0.0_ since the major version _1.x_. Some of these are breaking changes.
-
-The most relevant change is that the package name has been changed from `github.com/apenella/go-ansible` to `github.com/apenella/go-ansible/v2`. So, you need to update your import paths to use the new module name.
-Another important change to highlight is that command structs no longer execute commands. So, `AnsiblePlaybookCmd` and `AnsibleAdhocCmd` do not require an `Executor` anymore. Instead, the `Executor` is responsible for the command execution. To achieve that, the `Executor` depends on the command structs to generate the commands to execute.
-That change is motivated by the need of segregating the command generation from the command execution. Having the `Executor` as the central component of the command execution process allows the `Executor` to be more flexible and customizable. The _go-ansible_ library provides a set of decorator structs to configure the `Executor` with different features, such as stdout callback management, and Ansible configuration settings.
-
-Proceed through the following sections to understand the changes in version _2.x_ and learn how to adapt your code accordingly.
-
 ## Changes on the interfaces
 
 The version _v2.x_ introduces several changes in the interfaces used by the _go-ansible_ library. Throughout this document, you will find references to these interfaces and this section presents the new interfaces and where to find them.
 
 ### Added _Cmder_ interface
 
-The `Cmder` interface defined in _github.com/apenella/go-ansible/v2/internal/executable/os/exec_ and it is used to run external commands. The `os/exec` package implements the `Cmder` interface. The [Executabler](#added-executabler-interface)'s `Command` and `CommandContext` methods return a `Cmder` interface.
+The `Cmder` interface is defined in _github.com/apenella/go-ansible/v2/internal/executable/os/exec_ and it is used to run external commands. The `os/exec` package implements the `Cmder` interface. The [Executabler](#added-executabler-interface)'s `Command` and `CommandContext` methods return a `Cmder` interface.
 You can find the definition of the `Cmder` interface below:
 
 ```go
@@ -101,7 +101,7 @@ type Executabler interface {
 
 ### Added _ExecutorEnvVarSetter_ interface
 
-The `ExecutorEnvVarSetter` interface defined in _github.com/apenella/go-ansible/v2/pkg/execute/configuration_ defines an executor interface that you can set environment variables to. It is required by `AnsibleWithConfigurationSettingsExecute` decorator struct.
+The `ExecutorEnvVarSetter` interface defined in _github.com/apenella/go-ansible/v2/pkg/execute/configuration_ defines an executor interface to which you can set environment variables. It is required by `AnsibleWithConfigurationSettingsExecute` decorator struct.
 
 ```go
 // ExecutorEnvVarSetter extends the executor interface by adding methods to configure environment variables
@@ -191,7 +191,7 @@ Select the appropriate mechanism based on the stdout callback plugin you are usi
 
 To replace the _resultsFunc_, introduce an attribute of type `ResultsOutputer` in your executor. Utilize this attribute to print the results output from command execution. For an example of how the `DefaultExecute` struct has been adapted to use a `ResultsOutputer` for printing execution results, refer [Here](#adding-output-attribute-for-printing-execution-results).
 
-You can also read the section [Managing Ansible Stdout Callback](#managing-ansible-stdout-callback) to learn how to benefit of the stdout callback management structs provided by the _go-ansible_ library.
+You can also read the section [Managing Ansible Stdout Callback](#managing-ansible-stdout-callback) to learn how to benefit from the stdout callback management structs provided by the _go-ansible_ library.
 
 ### Replacing the _options_ argument
 
@@ -208,14 +208,16 @@ In version _v2.x_ you need to instantiate the `DefaultExecute` struct to execute
 
 ```go
 // Define the AnsiblePlaybookCmd and the required options.
-playbookCmd := &playbook.AnsiblePlaybookCmd{
-  Playbooks:         []string{"site.yml", "site2.yml"},
-  PlaybookOptions:           &playbook.AnsiblePlaybookOptions{
-    Connection: "local",
-    Inventory: "all,",
-  },
+ansiblePlaybookOptions := &playbook.AnsiblePlaybookOptions{
+  Connection: "local",
+  Inventory: "all,",
 }
 
+playbookCmd := playbook.NewAnsiblePlaybookCmd(
+  playbook.WithPlaybooks("site.yml", "site2.yml"),
+  playbook.WithPlaybookOptions(ansiblePlaybookOptions),
+)
+
 // playbookCmd is the Commander responsible for generating the command to execute
 exec := execute.NewDefaultExecute(
   execute.WithCmd(playbookCmd),
@@ -240,13 +242,16 @@ When instantiating the `DefaultExecute` struct, provide the `Cmd` attribute with
 
 ```go
 // Define the AnsiblePlaybookCmd and the required options.
-playbookCmd := &playbook.AnsiblePlaybookCmd{
-  Playbooks:         []string{"site.yml", "site2.yml"},
-  PlaybookOptions:           &playbook.AnsiblePlaybookOptions{
-    Connection: "local",
-    Inventory: "all,",
-  },
+ansiblePlaybookOptions := &playbook.AnsiblePlaybookOptions{
+  Connection: "local",
+  Inventory: "all,",
 }
+
+playbookCmd := playbook.NewAnsiblePlaybookCmd(
+  playbook.WithPlaybooks("site.yml", "site2.yml"),
+  playbook.WithPlaybookOptions(ansiblePlaybookOptions),
+)
+
 // Instanciate a DefaultExecutoe by providing 'playbookCmd' as the Commander.
 exec := execute.NewDefaultExecute(
   execute.WithCmd(playbookCmd),
@@ -259,20 +264,22 @@ In the above example, `playbookCmd` is of type `Commander`. The `Cmd` value is s
 
 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.
 
-If you need a custom executabler, it must implement the `Executabler` interface. Learn more about the `Executabler` interface [here](#added-executabler-interface).
+If you need a custom _executabler_, it must implement the `Executabler` interface. Learn more about the `Executabler` interface [here](#added-executabler-interface).
 
 The example below illustrates how to instantiate a `DefaultExecute` struct with a custom `Executabler`:
 
 ```go
 // Define the AnsiblePlaybookCmd and the required options.
-playbookCmd := &playbook.AnsiblePlaybookCmd{
-  Playbooks:         []string{"site.yml", "site2.yml"},
-  PlaybookOptions:           &playbook.AnsiblePlaybookOptions{
-    Connection: "local",
-    Inventory: "all,",
-  },
+ansiblePlaybookOptions := &playbook.AnsiblePlaybookOptions{
+  Connection: "local",
+  Inventory: "all,",
 }
 
+playbookCmd := playbook.NewAnsiblePlaybookCmd(
+  playbook.WithPlaybooks("site.yml", "site2.yml"),
+  playbook.WithPlaybookOptions(ansiblePlaybookOptions),
+)
+
 // Define a custom Executabler
 executable := &myCustomExecutabler{}
 
@@ -283,7 +290,7 @@ executor := execute.NewDefaultExecute(
 )
 ```
 
-In the example, `executable` implements the `Executabler` interface. When creating a new `DefaultExecute`, set the value of `Exec` through the `WithExecutable` function. The `DefaultExecute` will then use executable to execute the command.
+In the example, `executable` implements the `Executabler` interface. When creating a new `DefaultExecute`, set the value of `Exec` through the `WithExecutable` function. The `DefaultExecute` will then use the `executable` to execute the command.
 
 ### Adding _Output_ attribute for printing execution results
 
@@ -295,13 +302,16 @@ The example below demonstrates how to instantiate a `DefaultExecute` struct with
 
 ```go
 // Define the AnsiblePlaybookCmd and the required options.
-playbookCmd := &playbook.AnsiblePlaybookCmd{
-  Playbooks:         []string{"site.yml", "site2.yml"},
-  PlaybookOptions:           &playbook.AnsiblePlaybookOptions{
-    Connection: "local",
-    Inventory: "all,",
-  },
+ansiblePlaybookOptions := &playbook.AnsiblePlaybookOptions{
+  Connection: "local",
+  Inventory: "all,",
 }
+
+playbookCmd := playbook.NewAnsiblePlaybookCmd(
+  playbook.WithPlaybooks("site.yml", "site2.yml"),
+  playbook.WithPlaybookOptions(ansiblePlaybookOptions),
+)
+
 // Define a custom ResultsOutputer
 output := &myCustomResultsOutputer{}
 
@@ -339,11 +349,11 @@ fmt.Println("Duration: ", exec.Duration().String())
 
 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.
 
-Refer to section [Changes on the _Transformer_ functions](#changes-on-the-transformer-functions) for more details on how to adapt your code to these changes.
+Refer to the section [Changes on the _Transformer_ functions](#changes-on-the-transformer-functions) for more details on how to adapt your code to these changes.
 
 ## Changes on the _AnsiblePlaybookCmd_ struct
 
-The `AnsiblePlaybookCmd` struct has undergone significant changes. It no longer executes commands, instead, it now implements the `Commander` interface, which is responsible for generating commands for execution. This section provides guidance on adapting your code to these changes.
+The `AnsiblePlaybookCmd` struct has undergone significant changes. It no longer executes commands, instead, it now implements the `Commander` interface, which is responsible for generating commands for execution. This section guides adapting your code to these changes.
 
 ### Renaming the _Options_ attribute
 
@@ -357,14 +367,16 @@ Here's a basic example of running an ansible-playbook command through the `Defau
 
 ```go
 // Define the AnsiblePlaybookCmd and the required options.
-playbookCmd := &playbook.AnsiblePlaybookCmd{
-  Playbooks:         []string{"site.yml", "site2.yml"},
-  Options:           &playbook.AnsiblePlaybookOptions{
-    Connection: "local",
-    Inventory: "127.0.0.1,",
-  },
+ansiblePlaybookOptions := &playbook.AnsiblePlaybookOptions{
+  Connection: "local",
+  Inventory: "127.0.0.1,",
 }
 
+playbookCmd := playbook.NewAnsiblePlaybookCmd(
+  playbook.WithPlaybooks("site.yml", "site2.yml"),
+  playbook.WithPlaybookOptions(ansiblePlaybookOptions),
+)
+
 // Instanciate a DefaultExecutoe by providing 'playbookCmd' as the Commander
 exec := execute.NewDefaultExecute(
   execute.WithCmd(playbookCmd),
@@ -387,15 +399,16 @@ Configuring the stdout callback involves setting the environment variable `ANSIB
 
 ```go
 // Define the AnsiblePlaybookCmd and the required options.
-playbookCmd := &playbook.AnsiblePlaybookCmd{
-  Playbooks:         []string{"site.yml", "site2.yml"},
-    Connection: "local",
-  },
-  PlaybookOptions:           &playbook.AnsiblePlaybookOptions{
-    Inventory: "127.0.0.1,",
-  },
+ansiblePlaybookOptions := &playbook.AnsiblePlaybookOptions{
+  Connection: "local",
+  Inventory: "127.0.0.1,",
 }
 
+playbookCmd := playbook.NewAnsiblePlaybookCmd(
+  playbook.WithPlaybooks("site.yml", "site2.yml"),
+  playbook.WithPlaybookOptions(ansiblePlaybookOptions),
+)
+
 // Use the DefaultExecute struct to execute the command
 exec := execute.NewDefaultExecute(
   execute.WithCmd(playbookCmd),
@@ -443,11 +456,11 @@ Similar to the changes made to the `AnsiblePlaybookCmd` struct, the `AnsibleAdho
 
 ## Changes on the _AnsibleInventoryCmd_ struct
 
-The `AnsibleInventoryCmd` have undergone significant changes. It no longer executes commands, instead, it now implements the `Commander` interface, which is responsible for generating commands for execution. To adapt your code to these changes, refer to the guidelines provided in the section [Changes on the _AnsiblePlaybookCmd_ struct](#changes-on-the-ansibleplaybookcmd-struct).
+The `AnsibleInventoryCmd` has undergone significant changes. It no longer executes commands, instead, it now implements the `Commander` interface, which is responsible for generating commands for execution. To adapt your code to these changes, refer to the guidelines provided in the section [Changes on the _AnsiblePlaybookCmd_ struct](#changes-on-the-ansibleplaybookcmd-struct).
 
 ## Changes on the _Transformer_ functions
 
-In version _v2.0.0_, the _github.com/apenella/go-ansible/pkg/stdoutcallback/results_ package has been removed. This package previously contained the transformer functions responsible for modifying the output lines of the execution's results. This section provides guidance on how to adapt your code to these changes.
+In version _v2.0.0_, the _github.com/apenella/go-ansible/pkg/stdoutcallback/results_ package has been removed. This package previously contained the transformer functions responsible for modifying the output lines of the execution's results. This section guides how to adapt your code to these changes.
 
 To adapt your code, you should update the imported package to _github.com/apenella/go-ansible/v2/pkg/execute/result/transformer_. This is the new location of the transformer functions.
 
@@ -467,7 +480,7 @@ Configuring the StdoutCallback method involves two steps:
 - Set the `ANSIBLE_STDOUT_CALLBACK` environment variable to the desired stdout callback plugin name.
 - Set the method responsible for handling the results output from command execution. The responsibility of setting the StdoutCallback method has shifted to the `Executor` struct, necessitating an adjustment in your code.
 
-To simplify stdout callback configuration, the _go-ansible_ library provides a set of structs dedicated to setting the stdout callback method and results output mechanism. Each struct corresponds to a stdout callback plugin and is available in the _github.com/apenella/go-ansible/v2/pkg/execute/stdoutcallback_ package. The following is a list of available structs:
+To simplify the stdout callback configuration, the _go-ansible_ library provides a set of structs dedicated to setting the stdout callback method and results output mechanism. Each struct corresponds to a stdout callback plugin and is available in the _github.com/apenella/go-ansible/v2/pkg/execute/stdoutcallback_ package. The following is a list of available structs:
 
 - DebugStdoutCallbackExecute
 - DefaultStdoutCallbackExecute
@@ -506,14 +519,16 @@ Here's an example illustrating how to prepare an executor to set Ansible configu
 
 ```go
 // Define the AnsiblePlaybookCmd and the required options.
-playbookCmd := &playbook.AnsiblePlaybookCmd{
-  Playbooks:         []string{"site.yml", "site2.yml"},
-  PlaybookOptions:           &playbook.AnsiblePlaybookOptions{
-    Connection: "local",
-    Inventory: "127.0.0.1,",
-  },
+ansiblePlaybookOptions := &playbook.AnsiblePlaybookOptions{
+  Connection: "local",
+  Inventory: "127.0.0.1,",
 }
 
+playbookCmd := playbook.NewAnsiblePlaybookCmd(
+  playbook.WithPlaybooks("site.yml", "site2.yml"),
+  playbook.WithPlaybookOptions(ansiblePlaybookOptions),
+)
+
 // Instanciate a DefaultExecutoe by providing 'playbookCmd' as the Commander and enabling the Ansible Force Color setting
 exec := measure.NewExecutorTimeMeasurement(
   configuration.NewAnsibleWithConfigurationSettingsExecute(
@@ -570,7 +585,7 @@ In case you need to enable the _AnsibleHostKeyChecking_ setting, you should use
 #### Replacing the _AnsibleSetEnv_ function
 
 If you used the `AnsibleSetEnv` function to set environment variables for the _executor_, you should replace it by the `AddEnvVar` method.
-In case you were using the `AnsibleSetEnv` to set an _Ansible_ configuration setting, it is recommended to use the `AnsibleWithConfigurationSettingsExecute` _executor_ instead. The `AnsibleWithConfigurationSettingsExecute` struct is available in the _github.com/apenella/go-ansible/v2/pkg/execute/configuration_ package, and provides you with multiple functions to configure _Ansible_ settings.
+In case you were using the `AnsibleSetEnv` to set an _Ansible_ configuration setting, it is recommended to use the `AnsibleWithConfigurationSettingsExecute` _executor_ instead. The `AnsibleWithConfigurationSettingsExecute` struct is available in the _github.com/apenella/go-ansible/v2/pkg/execute/configuration_ package and provides you with multiple functions to configure _Ansible_ settings.
 
 If you were previously using the `AnsibleSetEnv` function to set environment variables for the _executor_, you should replace it with the `AddEnvVar` method.
 
diff --git a/examples/ansibleadhoc-command-module/ansibleadhoc-command-module.go b/examples/ansibleadhoc-command-module/ansibleadhoc-command-module.go
index 3003909..116f849 100644
--- a/examples/ansibleadhoc-command-module/ansibleadhoc-command-module.go
+++ b/examples/ansibleadhoc-command-module/ansibleadhoc-command-module.go
@@ -18,16 +18,16 @@ func main() {
 		ModuleName: "command",
 	}
 
-	adhoc := &adhoc.AnsibleAdhocCmd{
-		Pattern:      "all",
-		AdhocOptions: ansibleAdhocOptions,
-	}
+	adhocCmd := adhoc.NewAnsibleAdhocCmd(
+		adhoc.WithPattern("all"),
+		adhoc.WithAdhocOptions(ansibleAdhocOptions),
+	)
 
-	fmt.Println("Command: ", adhoc.String())
+	fmt.Println("Command: ", adhocCmd.String())
 
 	onelineExecute := stdoutcallback.NewOnelineStdoutCallbackExecute(
 		execute.NewDefaultExecute(
-			execute.WithCmd(adhoc),
+			execute.WithCmd(adhocCmd),
 		),
 	)
 
diff --git a/examples/ansibleinventory-graph/ansibleinventory-graph.go b/examples/ansibleinventory-graph/ansibleinventory-graph.go
index 1fed8a3..8063ea2 100644
--- a/examples/ansibleinventory-graph/ansibleinventory-graph.go
+++ b/examples/ansibleinventory-graph/ansibleinventory-graph.go
@@ -16,10 +16,10 @@ func main() {
 		Yaml:      true,
 	}
 
-	inventoryCmd := &inventory.AnsibleInventoryCmd{
-		Pattern:          "all",
-		InventoryOptions: &ansibleInventoryOptions,
-	}
+	inventoryCmd := inventory.NewAnsibleInventoryCmd(
+		inventory.WithPattern("all"),
+		inventory.WithInventoryOptions(&ansibleInventoryOptions),
+	)
 
 	fmt.Println("Test strings:", inventoryCmd.String())
 
diff --git a/examples/ansibleinventory-vaulted-vars/ansible-inventory-vaulted-vars.go b/examples/ansibleinventory-vaulted-vars/ansible-inventory-vaulted-vars.go
index 448cff9..dd08904 100644
--- a/examples/ansibleinventory-vaulted-vars/ansible-inventory-vaulted-vars.go
+++ b/examples/ansibleinventory-vaulted-vars/ansible-inventory-vaulted-vars.go
@@ -17,10 +17,10 @@ func main() {
 		VaultPasswordFile: "vault_password.cfg",
 	}
 
-	inventoryCmd := &inventory.AnsibleInventoryCmd{
-		Pattern:          "all",
-		InventoryOptions: &ansibleInventoryOptions,
-	}
+	inventoryCmd := inventory.NewAnsibleInventoryCmd(
+		inventory.WithPattern("all"),
+		inventory.WithInventoryOptions(&ansibleInventoryOptions),
+	)
 
 	fmt.Println("Test strings:", inventoryCmd.String())
 
diff --git a/examples/ansibleplaybook-become/ansibleplaybook-become.go b/examples/ansibleplaybook-become/ansibleplaybook-become.go
index 2751d3d..efb146e 100644
--- a/examples/ansibleplaybook-become/ansibleplaybook-become.go
+++ b/examples/ansibleplaybook-become/ansibleplaybook-become.go
@@ -17,13 +17,13 @@ func main() {
 		AskBecomePass: true,
 	}
 
-	playbook := &playbook.AnsiblePlaybookCmd{
-		Playbooks:       []string{"site.yml"},
-		PlaybookOptions: ansiblePlaybookOptions,
-	}
+	playbookCmd := playbook.NewAnsiblePlaybookCmd(
+		playbook.WithPlaybooks("site.yml"),
+		playbook.WithPlaybookOptions(ansiblePlaybookOptions),
+	)
 
 	exec := execute.NewDefaultExecute(
-		execute.WithCmd(playbook),
+		execute.WithCmd(playbookCmd),
 		execute.WithTransformers(
 			transformer.Prepend("Go-ansible example with become"),
 		),
diff --git a/examples/ansibleplaybook-cobra-cmd/ansibleplaybook-cobra-cmd.go b/examples/ansibleplaybook-cobra-cmd/ansibleplaybook-cobra-cmd.go
index a1b294e..0c52748 100644
--- a/examples/ansibleplaybook-cobra-cmd/ansibleplaybook-cobra-cmd.go
+++ b/examples/ansibleplaybook-cobra-cmd/ansibleplaybook-cobra-cmd.go
@@ -69,17 +69,17 @@ func commandHandler(cmd *cobra.Command, args []string) error {
 	}
 
 	for keyVar, valueVar := range vars {
-		ansiblePlaybookOptions.AddExtraVar(keyVar, valueVar)
+		_ = ansiblePlaybookOptions.AddExtraVar(keyVar, valueVar)
 	}
 
-	playbook := &playbook.AnsiblePlaybookCmd{
-		Playbooks:       playbookFiles,
-		PlaybookOptions: ansiblePlaybookOptions,
-	}
+	playbookCmd := playbook.NewAnsiblePlaybookCmd(
+		playbook.WithPlaybooks(playbookFiles...),
+		playbook.WithPlaybookOptions(ansiblePlaybookOptions),
+	)
 
 	exec := configuration.NewAnsibleWithConfigurationSettingsExecute(
 		execute.NewDefaultExecute(
-			execute.WithCmd(playbook),
+			execute.WithCmd(playbookCmd),
 			execute.WithTransformers(
 				transformer.Prepend("Go-ansible example with become"),
 			),
diff --git a/examples/ansibleplaybook-custom-transformer/ansibleplaybook-custom-transformer.go b/examples/ansibleplaybook-custom-transformer/ansibleplaybook-custom-transformer.go
index 2324ade..6ad9b7f 100644
--- a/examples/ansibleplaybook-custom-transformer/ansibleplaybook-custom-transformer.go
+++ b/examples/ansibleplaybook-custom-transformer/ansibleplaybook-custom-transformer.go
@@ -31,14 +31,14 @@ func main() {
 		Inventory:  "127.0.0.1,",
 	}
 
-	playbook := &playbook.AnsiblePlaybookCmd{
-		Playbooks:       []string{"site.yml"},
-		PlaybookOptions: ansiblePlaybookOptions,
-	}
+	playbookCmd := playbook.NewAnsiblePlaybookCmd(
+		playbook.WithPlaybooks("site.yml"),
+		playbook.WithPlaybookOptions(ansiblePlaybookOptions),
+	)
 
 	exec := configuration.NewAnsibleWithConfigurationSettingsExecute(
 		execute.NewDefaultExecute(
-			execute.WithCmd(playbook),
+			execute.WithCmd(playbookCmd),
 			execute.WithTransformers(
 				outputColored(),
 				transformer.Prepend("Go-ansible example"),
diff --git a/examples/ansibleplaybook-extravars-file/ansibleplaybook-extravars-file.go b/examples/ansibleplaybook-extravars-file/ansibleplaybook-extravars-file.go
index 8760691..faa13d8 100644
--- a/examples/ansibleplaybook-extravars-file/ansibleplaybook-extravars-file.go
+++ b/examples/ansibleplaybook-extravars-file/ansibleplaybook-extravars-file.go
@@ -19,13 +19,13 @@ func main() {
 		},
 	}
 
-	playbook := &playbook.AnsiblePlaybookCmd{
-		Playbooks:       []string{"site.yml"},
-		PlaybookOptions: ansiblePlaybookOptions,
-	}
+	playbookCmd := playbook.NewAnsiblePlaybookCmd(
+		playbook.WithPlaybooks("site.yml"),
+		playbook.WithPlaybookOptions(ansiblePlaybookOptions),
+	)
 
 	exec := execute.NewDefaultExecute(
-		execute.WithCmd(playbook),
+		execute.WithCmd(playbookCmd),
 	)
 
 	err := exec.Execute(context.TODO())
diff --git a/examples/ansibleplaybook-extravars/Makefile b/examples/ansibleplaybook-extravars/Makefile
new file mode 100644
index 0000000..07c979b
--- /dev/null
+++ b/examples/ansibleplaybook-extravars/Makefile
@@ -0,0 +1,45 @@
+
+DOCKER_COMPOSE_BINARY := $(shell docker compose version > /dev/null 2>&1 && echo "docker compose" || (which docker-compose > /dev/null 2>&1 && echo "docker-compose" || (echo "docker compose not found. Aborting." >&2; exit 1)))
+
+PROJECT_NAME := go-ansible-$(shell basename ${PWD})
+
+# dafault target
+.DEFAULT_GOAL: help
+
+help: ## Lists available targets
+	@echo
+	@echo "Makefile usage:"
+	@grep -E '^[a-zA-Z1-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "  \033[1;32m%-20s\033[0m %s\n", $$1, $$2}'
+	@echo
+
+build: ## Build the Docker compose environment
+	@$(DOCKER_COMPOSE_BINARY) build
+
+generate-keys: cleanup-keys ## Generate the SSH key pair required to autheneticate to SSH server
+	@$(DOCKER_COMPOSE_BINARY) --project-name $(PROJECT_NAME) run --rm openssh-client -t rsa -q -N "" -f id_rsa -C "apenella@$(PROJECT_NAME).test"
+
+cleanup-keys: ## Cleanup the SSH key pair
+	@$(DOCKER_COMPOSE_BINARY) --project-name $(PROJECT_NAME) run --rm --entrypoint /bin/sh openssh-client -c 'rm -rf $$(ls)'
+
+up: generate-keys ## Create and start containers
+	@$(DOCKER_COMPOSE_BINARY) --project-name $(PROJECT_NAME) up --detach --build
+
+down: ## Stop and remove containers, networks, and volumes
+	@$(DOCKER_COMPOSE_BINARY) --project-name $(PROJECT_NAME) down --volumes --remove-orphans --timeout 3
+
+restart: down up ## Restart the containers
+
+ps: ## List containers
+	@$(DOCKER_COMPOSE_BINARY) --project-name $(PROJECT_NAME) ps
+
+attach-ansible: ## Attach to the ansible container
+	@$(DOCKER_COMPOSE_BINARY) --project-name $(PROJECT_NAME) exec --workdir /code/examples/$$(basename $$(pwd)) ansible /bin/sh
+
+attach-server: ## Attach to the server container
+	@$(DOCKER_COMPOSE_BINARY) --project-name $(PROJECT_NAME) exec server /bin/sh
+
+project-name: ## Show the project name
+	@echo $(PROJECT_NAME)
+
+run: ## Run the playbook
+	@$(DOCKER_COMPOSE_BINARY) --project-name $(PROJECT_NAME) exec --workdir /code/examples/$$(basename $$(pwd)) ansible go run $$(basename $$(pwd)).go
\ No newline at end of file
diff --git a/examples/ansibleplaybook-extravars/README.md b/examples/ansibleplaybook-extravars/README.md
new file mode 100644
index 0000000..06f7473
--- /dev/null
+++ b/examples/ansibleplaybook-extravars/README.md
@@ -0,0 +1,11 @@
+# Example ansibleplaybook-ssh
+
+This example demonstrates how to use `go-ansible` to run an Ansible playbook on a remote host using SSH.
+
+The example provides a `Makefile` to build and run the example. The `Makefile` includes the following targets:
+
+- `build`: Builds the containers to run the example
+- `run`: Runs the example
+- `up`: Starts the environment
+- `down`: Stops the environment
+- `restart`: Restarts the environment
diff --git a/examples/ansibleplaybook-extravars/ansibleplaybook-extravars.go b/examples/ansibleplaybook-extravars/ansibleplaybook-extravars.go
new file mode 100644
index 0000000..67b1a21
--- /dev/null
+++ b/examples/ansibleplaybook-extravars/ansibleplaybook-extravars.go
@@ -0,0 +1,41 @@
+package main
+
+import (
+	"context"
+	"fmt"
+
+	"github.com/apenella/go-ansible/v2/pkg/execute"
+	"github.com/apenella/go-ansible/v2/pkg/execute/stdoutcallback"
+	"github.com/apenella/go-ansible/v2/pkg/playbook"
+)
+
+func main() {
+
+	ansiblePlaybookOptions := &playbook.AnsiblePlaybookOptions{
+		ExtraVars: map[string]interface{}{
+			"extravar1":    "value11",
+			"extravar2":    "value12",
+			"ansible_port": "22225",
+		},
+		Inventory:     "inventory.yml",
+		SSHCommonArgs: "-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null",
+	}
+
+	playbookCmd := playbook.NewAnsiblePlaybookCmd(
+		playbook.WithPlaybooks("site.yml"),
+		playbook.WithPlaybookOptions(ansiblePlaybookOptions),
+	)
+
+	fmt.Println("Command: ", playbookCmd.String())
+
+	yamlexec := stdoutcallback.NewYAMLStdoutCallbackExecute(
+		execute.NewDefaultExecute(
+			execute.WithCmd(playbookCmd),
+		),
+	)
+
+	err := yamlexec.Execute(context.TODO())
+	if err != nil {
+		panic(err)
+	}
+}
diff --git a/examples/ansibleplaybook-extravars/docker-compose.yaml b/examples/ansibleplaybook-extravars/docker-compose.yaml
new file mode 100644
index 0000000..2763781
--- /dev/null
+++ b/examples/ansibleplaybook-extravars/docker-compose.yaml
@@ -0,0 +1,43 @@
+name: ansibleplaybook-ssh
+
+services:
+  ansible:
+    build: 
+      context: docker/ansible
+    command: ["tail", "-f", "/dev/null"]
+    # command: ["ansible-playbook", "--help"]
+    volumes:
+      - ssh:/ssh
+      - ../..:/code
+    working_dir: /code
+    ## Set the init flag to true lets the process 1 to reap all the zombie processes
+    init: true
+    depends_on:
+      - server
+
+  server:
+    build: 
+      context: docker/server
+      args:
+        - sshd_port=${SSHD_PORT:-22225}
+    # command: ["cat", "/etc/ssh/sshd_config"]
+    volumes:
+      - ssh:/ssh
+    depends_on:
+      - openssh-client
+    ports:
+      - "22220:${SSHD_PORT:-22225}"
+
+  openssh-client:
+    build: 
+      context: docker/openssh-client
+    working_dir: /ssh
+    volumes:
+      - ssh:/ssh
+    deploy:
+      resources:
+        limits:
+          memory: 10M
+
+volumes:
+  ssh:
\ No newline at end of file
diff --git a/examples/ansibleplaybook-extravars/docker/ansible/Dockerfile b/examples/ansibleplaybook-extravars/docker/ansible/Dockerfile
new file mode 100644
index 0000000..41c9850
--- /dev/null
+++ b/examples/ansibleplaybook-extravars/docker/ansible/Dockerfile
@@ -0,0 +1,25 @@
+FROM python:3.12-alpine3.19
+
+RUN apk add --update --no-cache \
+        openssh-client \
+        git \
+    && rm -rf /var/cache/apk/*
+
+RUN pip3 install -U pip setuptools \
+    && pip3 install --no-cache-dir \
+        setuptools-rust \
+        cryptography \
+        # Required library to execute ansible community.general.dig plugin
+        dnspython \
+        ansible \
+    && ln /usr/local/bin/ansible-playbook /usr/bin/ansible-playbook
+
+RUN apk add --update --no-cache \
+        go
+
+# Configure Go
+ENV GOROOT /usr/lib/go
+ENV GOPATH /go
+ENV PATH /go/bin:$PATH
+
+RUN mkdir -p ${GOPATH}/src ${GOPATH}/bin
diff --git a/examples/ansibleplaybook-extravars/docker/openssh-client/Dockerfile b/examples/ansibleplaybook-extravars/docker/openssh-client/Dockerfile
new file mode 100644
index 0000000..4736669
--- /dev/null
+++ b/examples/ansibleplaybook-extravars/docker/openssh-client/Dockerfile
@@ -0,0 +1,9 @@
+FROM alpine:3.19
+
+RUN apk add --update --no-cache \
+        openssh-client \
+        git \
+    && rm -rf /var/cache/apk/*
+
+ENTRYPOINT [ "/usr/bin/ssh-keygen" ]
+CMD [ "--help" ]
\ No newline at end of file
diff --git a/examples/ansibleplaybook-extravars/docker/server/Dockerfile b/examples/ansibleplaybook-extravars/docker/server/Dockerfile
new file mode 100644
index 0000000..ccae92b
--- /dev/null
+++ b/examples/ansibleplaybook-extravars/docker/server/Dockerfile
@@ -0,0 +1,20 @@
+FROM python:alpine3.18
+
+ARG sshd_port=2222
+
+RUN apk add --update --no-cache \
+        openssh-server \
+    # create a user
+    && adduser -D ansible \
+    && rm -rf /var/cache/apk/* \
+    && ssh-keygen -A
+
+RUN echo "Port ${sshd_port}" >> /etc/ssh/sshd_config
+
+COPY entrypoint.sh /usr/local/bin/entrypoint.sh
+RUN chmod +x /usr/local/bin/entrypoint.sh
+
+# Expose the SSH port
+EXPOSE ${sshd_port}
+
+CMD ["/usr/local/bin/entrypoint.sh"]
diff --git a/examples/ansibleplaybook-extravars/docker/server/entrypoint.sh b/examples/ansibleplaybook-extravars/docker/server/entrypoint.sh
new file mode 100644
index 0000000..722dc2f
--- /dev/null
+++ b/examples/ansibleplaybook-extravars/docker/server/entrypoint.sh
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+home=$(getent passwd "$(whoami)" |  cut -d: -f6)
+
+mkdir -p "${home}/.ssh"
+cat /ssh/id_rsa.pub > "${home}/.ssh/authorized_keys"
+
+/usr/sbin/sshd -D
\ No newline at end of file
diff --git a/examples/ansibleplaybook-extravars/inventory.yml b/examples/ansibleplaybook-extravars/inventory.yml
new file mode 100644
index 0000000..a186167
--- /dev/null
+++ b/examples/ansibleplaybook-extravars/inventory.yml
@@ -0,0 +1,9 @@
+---
+
+all:
+  hosts:
+    server:
+      ansible_host: server
+      ansible_user: root
+      ansible_ssh_private_key_file: /ssh/id_rsa
+
diff --git a/examples/ansibleplaybook-extravars/site.yml b/examples/ansibleplaybook-extravars/site.yml
new file mode 100644
index 0000000..c88ef87
--- /dev/null
+++ b/examples/ansibleplaybook-extravars/site.yml
@@ -0,0 +1,19 @@
+---
+
+- hosts: server
+  vars:
+    extravar1: na
+    extravar2: na
+
+  tasks:
+    - name: Gather facts
+      ansible.builtin.setup:
+        gather_subset:
+          - all_ipv4_addresses
+
+    - name: show values
+      debug:
+        msg: |
+          {{ hostvars['server']['ansible_hostname'] }}[{{ hostvars['server']['ansible_default_ipv4']['address'] }}] (port: {{ hostvars['server']['ansible_port'] }})
+          - {{ extravar1 }}
+          - {{ extravar2 }}
\ No newline at end of file
diff --git a/examples/ansibleplaybook-json-stdout/ansibleplaybook-json-stdout.go b/examples/ansibleplaybook-json-stdout/ansibleplaybook-json-stdout.go
index 3807889..9d48d47 100644
--- a/examples/ansibleplaybook-json-stdout/ansibleplaybook-json-stdout.go
+++ b/examples/ansibleplaybook-json-stdout/ansibleplaybook-json-stdout.go
@@ -26,15 +26,16 @@ func main() {
 	}
 
 	playbooksList := []string{"site1.yml", "site2.yml", "site3.yml"}
-	playbook := &playbook.AnsiblePlaybookCmd{
-		Playbooks:       playbooksList,
-		PlaybookOptions: ansiblePlaybookOptions,
-	}
+
+	playbookCmd := playbook.NewAnsiblePlaybookCmd(
+		playbook.WithPlaybooks(playbooksList...),
+		playbook.WithPlaybookOptions(ansiblePlaybookOptions),
+	)
 
 	exec := measure.NewExecutorTimeMeasurement(
 		stdoutcallback.NewJSONStdoutCallbackExecute(
 			execute.NewDefaultExecute(
-				execute.WithCmd(playbook),
+				execute.WithCmd(playbookCmd),
 				execute.WithWrite(io.Writer(buff)),
 			),
 		),
diff --git a/examples/ansibleplaybook-myexecutor/ansibleplaybook-myexecutor.go b/examples/ansibleplaybook-myexecutor/ansibleplaybook-myexecutor.go
index a207f17..58f6c17 100644
--- a/examples/ansibleplaybook-myexecutor/ansibleplaybook-myexecutor.go
+++ b/examples/ansibleplaybook-myexecutor/ansibleplaybook-myexecutor.go
@@ -36,12 +36,12 @@ func main() {
 		Inventory:  "127.0.0.1,",
 	}
 
-	playbook := &playbook.AnsiblePlaybookCmd{
-		Playbooks:       []string{"site.yml"},
-		PlaybookOptions: ansiblePlaybookOptions,
-	}
+	playbookCmd := playbook.NewAnsiblePlaybookCmd(
+		playbook.WithPlaybooks("site.yml"),
+		playbook.WithPlaybookOptions(ansiblePlaybookOptions),
+	)
 
-	exec := NewMyExecutor(playbook)
+	exec := NewMyExecutor(playbookCmd)
 	exec.WithPrefix("[Go ansible example]")
 
 	err := exec.Execute(context.TODO())
diff --git a/examples/ansibleplaybook-signals-and-cancellation/ansibleplaybook-signals-and-cancellation.go b/examples/ansibleplaybook-signals-and-cancellation/ansibleplaybook-signals-and-cancellation.go
index 85762a2..e057772 100644
--- a/examples/ansibleplaybook-signals-and-cancellation/ansibleplaybook-signals-and-cancellation.go
+++ b/examples/ansibleplaybook-signals-and-cancellation/ansibleplaybook-signals-and-cancellation.go
@@ -23,14 +23,14 @@ func main() {
 		Inventory:  "127.0.0.1,",
 	}
 
-	playbook := &playbook.AnsiblePlaybookCmd{
-		Playbooks:       []string{"site.yml"},
-		PlaybookOptions: ansiblePlaybookOptions,
-	}
+	playbookCmd := playbook.NewAnsiblePlaybookCmd(
+		playbook.WithPlaybooks("site.yml"),
+		playbook.WithPlaybookOptions(ansiblePlaybookOptions),
+	)
 
 	exec := configuration.NewAnsibleWithConfigurationSettingsExecute(
 		execute.NewDefaultExecute(
-			execute.WithCmd(playbook),
+			execute.WithCmd(playbookCmd),
 			execute.WithTransformers(
 				transformer.Prepend("[ansibleplaybook-signals-and-cancellation]"),
 			),
diff --git a/examples/ansibleplaybook-simple-embedfs/ansibleplaybook-simple-embedfs.go b/examples/ansibleplaybook-simple-embedfs/ansibleplaybook-simple-embedfs.go
index 1a520b5..5f070ca 100644
--- a/examples/ansibleplaybook-simple-embedfs/ansibleplaybook-simple-embedfs.go
+++ b/examples/ansibleplaybook-simple-embedfs/ansibleplaybook-simple-embedfs.go
@@ -5,7 +5,6 @@ import (
 	"embed"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"os"
 	"path/filepath"
 
@@ -19,7 +18,7 @@ var playbooks embed.FS
 func main() {
 
 	// Create a temporary directory with a prefix "ansibleplaybook-simple-embedfs-"
-	tempDir, err := ioutil.TempDir("", "ansibleplaybook-simple-embedfs-")
+	tempDir, err := os.MkdirTemp("", "ansibleplaybook-simple-embedfs-")
 	if err != nil {
 		panic(err)
 	}
@@ -37,13 +36,13 @@ func main() {
 		Inventory:  filepath.Join(tempDir, "inventory.ini"),
 	}
 
-	playbook := &playbook.AnsiblePlaybookCmd{
-		Playbooks:       []string{filepath.Join(tempDir, "site.yml"), filepath.Join(tempDir, "site2.yml")},
-		PlaybookOptions: ansiblePlaybookOptions,
-	}
+	playbookCmd := playbook.NewAnsiblePlaybookCmd(
+		playbook.WithPlaybooks(filepath.Join(tempDir, "site.yml"), filepath.Join(tempDir, "site2.yml")),
+		playbook.WithPlaybookOptions(ansiblePlaybookOptions),
+	)
 
 	exec := execute.NewDefaultExecute(
-		execute.WithCmd(playbook),
+		execute.WithCmd(playbookCmd),
 	)
 
 	err = exec.Execute(context.TODO())
diff --git a/examples/ansibleplaybook-simple-with-prompt/ansibleplaybook-simple-with-prompt.go b/examples/ansibleplaybook-simple-with-prompt/ansibleplaybook-simple-with-prompt.go
index 37bfe9f..f0c9c19 100644
--- a/examples/ansibleplaybook-simple-with-prompt/ansibleplaybook-simple-with-prompt.go
+++ b/examples/ansibleplaybook-simple-with-prompt/ansibleplaybook-simple-with-prompt.go
@@ -14,13 +14,13 @@ func main() {
 		Inventory:  "127.0.0.1,",
 	}
 
-	playbook := &playbook.AnsiblePlaybookCmd{
-		Playbooks:       []string{"input.yml"},
-		PlaybookOptions: ansiblePlaybookOptions,
-	}
+	playbookCmd := playbook.NewAnsiblePlaybookCmd(
+		playbook.WithPlaybooks("input.yml"),
+		playbook.WithPlaybookOptions(ansiblePlaybookOptions),
+	)
 
 	exec := execute.NewDefaultExecute(
-		execute.WithCmd(playbook),
+		execute.WithCmd(playbookCmd),
 	)
 
 	err := exec.Execute(context.TODO())
diff --git a/examples/ansibleplaybook-skipping-failing/ansibleplaybook-skipping-failing.go b/examples/ansibleplaybook-skipping-failing/ansibleplaybook-skipping-failing.go
index 81f2517..90fb532 100644
--- a/examples/ansibleplaybook-skipping-failing/ansibleplaybook-skipping-failing.go
+++ b/examples/ansibleplaybook-skipping-failing/ansibleplaybook-skipping-failing.go
@@ -27,16 +27,17 @@ func main() {
 	}
 
 	playbooksList := []string{"site1.yml"}
-	playbook := &playbook.AnsiblePlaybookCmd{
-		Playbooks:       playbooksList,
-		PlaybookOptions: ansiblePlaybookOptions,
-	}
 
-	log.Println("Command: ", playbook)
+	playbookCmd := playbook.NewAnsiblePlaybookCmd(
+		playbook.WithPlaybooks(playbooksList...),
+		playbook.WithPlaybookOptions(ansiblePlaybookOptions),
+	)
+
+	log.Println("Command: ", playbookCmd.String())
 
 	exec := stdoutcallback.NewJSONStdoutCallbackExecute(
 		execute.NewDefaultExecute(
-			execute.WithCmd(playbook),
+			execute.WithCmd(playbookCmd),
 			execute.WithWrite(io.Writer(buff)),
 		),
 	)
diff --git a/examples/ansibleplaybook-ssh/Makefile b/examples/ansibleplaybook-ssh/Makefile
new file mode 100644
index 0000000..5c42d87
--- /dev/null
+++ b/examples/ansibleplaybook-ssh/Makefile
@@ -0,0 +1,45 @@
+
+DOCKER_COMPOSE_BINARY := $(shell docker compose version > /dev/null 2>&1 && echo "docker compose" || (which docker-compose > /dev/null 2>&1 && echo "docker-compose" || (echo "docker compose not found. Aborting." >&2; exit 1)))
+
+PROJECT_NAME := go-ansible-$(shell basename ${PWD})
+
+# dafault target
+.DEFAULT_GOAL: help
+
+help: ## Lists available targets
+	@echo
+	@echo "Makefile usage:"
+	@grep -E '^[a-zA-Z1-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "  \033[1;32m%-20s\033[0m %s\n", $$1, $$2}'
+	@echo
+
+build: ## Build the Docker compose environment
+	@$(DOCKER_COMPOSE_BINARY) build
+
+generate-keys: cleanup-keys ## Generate the SSH key pair required to autheneticate to SSH server
+	@$(DOCKER_COMPOSE_BINARY) --project-name $(PROJECT_NAME) run --rm openssh-client -t rsa -q -N "" -f id_rsa -C "apenella@$(PROJECT_NAME).test"
+
+cleanup-keys: ## Cleanup the SSH key pair
+	@$(DOCKER_COMPOSE_BINARY) --project-name $(PROJECT_NAME) run --rm --entrypoint /bin/sh openssh-client -c 'rm -rf $$(ls)'
+
+up: generate-keys ## Create and start containers
+	@$(DOCKER_COMPOSE_BINARY) --project-name $(PROJECT_NAME) up --detach --build
+
+down: ## Stop and remove containers, networks, and volumes
+	@$(DOCKER_COMPOSE_BINARY) --project-name $(PROJECT_NAME) down --volumes --remove-orphans --timeout 3
+
+restart: down up ## Restart the containers
+
+ps: ## List containers
+	@$(DOCKER_COMPOSE_BINARY) --project-name $(PROJECT_NAME) ps
+
+attach-ansible: ## Attach to the ansible container
+	@$(DOCKER_COMPOSE_BINARY) --project-name $(PROJECT_NAME) exec --workdir /code/examples/$$(basename $$(pwd)) ansible /bin/sh
+
+attach-server: ## Attach to the server container
+	@$(DOCKER_COMPOSE_BINARY) --project-name $(PROJECT_NAME) exec server /bin/sh
+
+project-name: ## Show the project name
+	@echo $(PROJECT_NAME)
+
+run: ## Run the playbook
+	@$(DOCKER_COMPOSE_BINARY) --project-name $(PROJECT_NAME) exec --workdir /code/examples/$$(basename $$(pwd)) ansible go run ansibleplaybook-ssh.go
\ No newline at end of file
diff --git a/examples/ansibleplaybook-ssh/README.md b/examples/ansibleplaybook-ssh/README.md
new file mode 100644
index 0000000..06f7473
--- /dev/null
+++ b/examples/ansibleplaybook-ssh/README.md
@@ -0,0 +1,11 @@
+# Example ansibleplaybook-ssh
+
+This example demonstrates how to use `go-ansible` to run an Ansible playbook on a remote host using SSH.
+
+The example provides a `Makefile` to build and run the example. The `Makefile` includes the following targets:
+
+- `build`: Builds the containers to run the example
+- `run`: Runs the example
+- `up`: Starts the environment
+- `down`: Stops the environment
+- `restart`: Restarts the environment
diff --git a/examples/ansibleplaybook-ssh/ansibleplaybook-ssh.go b/examples/ansibleplaybook-ssh/ansibleplaybook-ssh.go
new file mode 100644
index 0000000..c0bef72
--- /dev/null
+++ b/examples/ansibleplaybook-ssh/ansibleplaybook-ssh.go
@@ -0,0 +1,34 @@
+package main
+
+import (
+	"context"
+	"fmt"
+	"os"
+
+	"github.com/apenella/go-ansible/v2/pkg/execute"
+	"github.com/apenella/go-ansible/v2/pkg/playbook"
+)
+
+func main() {
+	ansiblePlaybookOptions := &playbook.AnsiblePlaybookOptions{
+		Inventory:     "inventory.yml",
+		SSHCommonArgs: "-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null",
+	}
+
+	cmd := playbook.NewAnsiblePlaybookCmd(
+		playbook.WithPlaybooks("site.yml"),
+		playbook.WithPlaybookOptions(ansiblePlaybookOptions),
+	)
+
+	exec := execute.NewDefaultExecute(
+		execute.WithCmd(cmd),
+	)
+
+	err := exec.Execute(context.TODO())
+	if err != nil {
+		os.Exit(1)
+	}
+
+	fmt.Println(cmd.String())
+
+}
diff --git a/examples/ansibleplaybook-ssh/docker-compose.yaml b/examples/ansibleplaybook-ssh/docker-compose.yaml
new file mode 100644
index 0000000..a082f7a
--- /dev/null
+++ b/examples/ansibleplaybook-ssh/docker-compose.yaml
@@ -0,0 +1,41 @@
+name: ansibleplaybook-ssh
+
+services:
+  ansible:
+    build: 
+      context: docker/ansible
+    command: ["tail", "-f", "/dev/null"]
+    # command: ["ansible-playbook", "--help"]
+    volumes:
+      - ssh:/ssh
+      - ../..:/code
+    working_dir: /code
+    ## Set the init flag to true lets the process 1 to reap all the zombie processes
+    init: true
+    depends_on:
+      - server
+
+  server:
+    build: 
+      context: docker/server
+    # command: ["cat", "/etc/ssh/sshd_config"]
+    volumes:
+      - ssh:/ssh
+    depends_on:
+      - openssh-client
+    ports:
+      - "22220:22"
+
+  openssh-client:
+    build: 
+      context: docker/openssh-client
+    working_dir: /ssh
+    volumes:
+      - ssh:/ssh
+    deploy:
+      resources:
+        limits:
+          memory: 10M
+
+volumes:
+  ssh:
\ No newline at end of file
diff --git a/examples/ansibleplaybook-ssh/docker/ansible/Dockerfile b/examples/ansibleplaybook-ssh/docker/ansible/Dockerfile
new file mode 100644
index 0000000..41c9850
--- /dev/null
+++ b/examples/ansibleplaybook-ssh/docker/ansible/Dockerfile
@@ -0,0 +1,25 @@
+FROM python:3.12-alpine3.19
+
+RUN apk add --update --no-cache \
+        openssh-client \
+        git \
+    && rm -rf /var/cache/apk/*
+
+RUN pip3 install -U pip setuptools \
+    && pip3 install --no-cache-dir \
+        setuptools-rust \
+        cryptography \
+        # Required library to execute ansible community.general.dig plugin
+        dnspython \
+        ansible \
+    && ln /usr/local/bin/ansible-playbook /usr/bin/ansible-playbook
+
+RUN apk add --update --no-cache \
+        go
+
+# Configure Go
+ENV GOROOT /usr/lib/go
+ENV GOPATH /go
+ENV PATH /go/bin:$PATH
+
+RUN mkdir -p ${GOPATH}/src ${GOPATH}/bin
diff --git a/examples/ansibleplaybook-ssh/docker/openssh-client/Dockerfile b/examples/ansibleplaybook-ssh/docker/openssh-client/Dockerfile
new file mode 100644
index 0000000..4736669
--- /dev/null
+++ b/examples/ansibleplaybook-ssh/docker/openssh-client/Dockerfile
@@ -0,0 +1,9 @@
+FROM alpine:3.19
+
+RUN apk add --update --no-cache \
+        openssh-client \
+        git \
+    && rm -rf /var/cache/apk/*
+
+ENTRYPOINT [ "/usr/bin/ssh-keygen" ]
+CMD [ "--help" ]
\ No newline at end of file
diff --git a/examples/ansibleplaybook-ssh/docker/server/Dockerfile b/examples/ansibleplaybook-ssh/docker/server/Dockerfile
new file mode 100644
index 0000000..9783902
--- /dev/null
+++ b/examples/ansibleplaybook-ssh/docker/server/Dockerfile
@@ -0,0 +1,16 @@
+FROM python:alpine3.18
+
+RUN apk add --update --no-cache \
+        openssh-server \
+    # create a user
+    && adduser -D ansible \
+    && rm -rf /var/cache/apk/* \
+    && ssh-keygen -A
+
+COPY entrypoint.sh /usr/local/bin/entrypoint.sh
+RUN chmod +x /usr/local/bin/entrypoint.sh
+
+# Expose the SSH port
+EXPOSE 22
+
+CMD ["/usr/local/bin/entrypoint.sh"]
diff --git a/examples/ansibleplaybook-ssh/docker/server/entrypoint.sh b/examples/ansibleplaybook-ssh/docker/server/entrypoint.sh
new file mode 100644
index 0000000..722dc2f
--- /dev/null
+++ b/examples/ansibleplaybook-ssh/docker/server/entrypoint.sh
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+home=$(getent passwd "$(whoami)" |  cut -d: -f6)
+
+mkdir -p "${home}/.ssh"
+cat /ssh/id_rsa.pub > "${home}/.ssh/authorized_keys"
+
+/usr/sbin/sshd -D
\ No newline at end of file
diff --git a/examples/ansibleplaybook-ssh/inventory.yml b/examples/ansibleplaybook-ssh/inventory.yml
new file mode 100644
index 0000000..a186167
--- /dev/null
+++ b/examples/ansibleplaybook-ssh/inventory.yml
@@ -0,0 +1,9 @@
+---
+
+all:
+  hosts:
+    server:
+      ansible_host: server
+      ansible_user: root
+      ansible_ssh_private_key_file: /ssh/id_rsa
+
diff --git a/examples/ansibleplaybook-ssh/site.yml b/examples/ansibleplaybook-ssh/site.yml
new file mode 100644
index 0000000..c18fcfc
--- /dev/null
+++ b/examples/ansibleplaybook-ssh/site.yml
@@ -0,0 +1,9 @@
+---
+
+- hosts: server
+
+  tasks:
+    - name: ansibleplaybook-ssh
+      ansible.builtin.debug:
+        msg: Your are running 'ansibleplaybook-ssh' example
+
diff --git a/examples/ansibleplaybook-time-measurement/ansibleplaybook-time-measurement.go b/examples/ansibleplaybook-time-measurement/ansibleplaybook-time-measurement.go
index b19279d..8f9e68f 100644
--- a/examples/ansibleplaybook-time-measurement/ansibleplaybook-time-measurement.go
+++ b/examples/ansibleplaybook-time-measurement/ansibleplaybook-time-measurement.go
@@ -17,15 +17,15 @@ func main() {
 		Inventory:  "127.0.0.1,",
 	}
 
-	playbook := &playbook.AnsiblePlaybookCmd{
-		Playbooks:       []string{"site.yml"},
-		PlaybookOptions: ansiblePlaybookOptions,
-	}
+	playbookCmd := playbook.NewAnsiblePlaybookCmd(
+		playbook.WithPlaybooks("site.yml"),
+		playbook.WithPlaybookOptions(ansiblePlaybookOptions),
+	)
 
 	exec := measure.NewExecutorTimeMeasurement(
 		configuration.NewAnsibleWithConfigurationSettingsExecute(
 			execute.NewDefaultExecute(
-				execute.WithCmd(playbook),
+				execute.WithCmd(playbookCmd),
 			),
 			configuration.WithAnsibleForceColor(),
 		),
diff --git a/examples/ansibleplaybook-walk-through-json-output/ansibleplaybook-walk-through-json-output.go b/examples/ansibleplaybook-walk-through-json-output/ansibleplaybook-walk-through-json-output.go
index be2a899..7bbc873 100644
--- a/examples/ansibleplaybook-walk-through-json-output/ansibleplaybook-walk-through-json-output.go
+++ b/examples/ansibleplaybook-walk-through-json-output/ansibleplaybook-walk-through-json-output.go
@@ -26,14 +26,14 @@ func main() {
 		Inventory:  "127.0.0.1,",
 	}
 
-	playbook := &playbook.AnsiblePlaybookCmd{
-		Playbooks:       []string{"site1.yml", "site2.yml"},
-		PlaybookOptions: ansiblePlaybookOptions,
-	}
+	playbookCmd := playbook.NewAnsiblePlaybookCmd(
+		playbook.WithPlaybooks("site1.yml", "site2.yml"),
+		playbook.WithPlaybookOptions(ansiblePlaybookOptions),
+	)
 
 	exec := stdoutcallback.NewJSONStdoutCallbackExecute(
 		execute.NewDefaultExecute(
-			execute.WithCmd(playbook),
+			execute.WithCmd(playbookCmd),
 			execute.WithWrite(io.Writer(buff)),
 		),
 	)
diff --git a/examples/ansibleplaybook-with-executor-time-measurament/ansibleplaybook-with-executor-time-measurament.go b/examples/ansibleplaybook-with-executor-time-measurament/ansibleplaybook-with-executor-time-measurament.go
index 2aef7f0..45fb4b3 100644
--- a/examples/ansibleplaybook-with-executor-time-measurament/ansibleplaybook-with-executor-time-measurament.go
+++ b/examples/ansibleplaybook-with-executor-time-measurament/ansibleplaybook-with-executor-time-measurament.go
@@ -18,15 +18,15 @@ func main() {
 		Inventory:  "127.0.0.1,",
 	}
 
-	playbook := &playbook.AnsiblePlaybookCmd{
-		Playbooks:       []string{"site.yml"},
-		PlaybookOptions: ansiblePlaybookOptions,
-	}
+	playbookCmd := playbook.NewAnsiblePlaybookCmd(
+		playbook.WithPlaybooks("site.yml"),
+		playbook.WithPlaybookOptions(ansiblePlaybookOptions),
+	)
 
 	exec := measure.NewExecutorTimeMeasurement(
 		configuration.NewAnsibleWithConfigurationSettingsExecute(
 			execute.NewDefaultExecute(
-				execute.WithCmd(playbook),
+				execute.WithCmd(playbookCmd),
 				execute.WithTransformers(
 					transformer.Prepend("Go-ansible example"),
 					transformer.LogFormat(transformer.DefaultLogFormatLayout, transformer.Now),
diff --git a/examples/ansibleplaybook-with-timeout/ansibleplaybook-with-timeout.go b/examples/ansibleplaybook-with-timeout/ansibleplaybook-with-timeout.go
index a867e73..d0437c8 100644
--- a/examples/ansibleplaybook-with-timeout/ansibleplaybook-with-timeout.go
+++ b/examples/ansibleplaybook-with-timeout/ansibleplaybook-with-timeout.go
@@ -28,13 +28,13 @@ func main() {
 		Inventory:  "127.0.0.1,",
 	}
 
-	playbook := &playbook.AnsiblePlaybookCmd{
-		Playbooks:       []string{"site.yml"},
-		PlaybookOptions: ansiblePlaybookOptions,
-	}
+	playbookCmd := playbook.NewAnsiblePlaybookCmd(
+		playbook.WithPlaybooks("site.yml"),
+		playbook.WithPlaybookOptions(ansiblePlaybookOptions),
+	)
 
 	exec := execute.NewDefaultExecute(
-		execute.WithCmd(playbook),
+		execute.WithCmd(playbookCmd),
 		execute.WithTransformers(
 			transformer.Prepend("Go-ansible example"),
 		),
diff --git a/examples/ansibleplaybook-with-vaulted-extravar/ansibleplaybook-with-vaulted-extravar.go b/examples/ansibleplaybook-with-vaulted-extravar/ansibleplaybook-with-vaulted-extravar.go
index b39f4b5..77c17fb 100644
--- a/examples/ansibleplaybook-with-vaulted-extravar/ansibleplaybook-with-vaulted-extravar.go
+++ b/examples/ansibleplaybook-with-vaulted-extravar/ansibleplaybook-with-vaulted-extravar.go
@@ -54,16 +54,16 @@ func main() {
 		panic(err)
 	}
 
-	playbook := &playbook.AnsiblePlaybookCmd{
-		Playbooks:       []string{"site.yml"},
-		PlaybookOptions: ansiblePlaybookOptions,
-	}
+	playbookCmd := playbook.NewAnsiblePlaybookCmd(
+		playbook.WithPlaybooks("site.yml"),
+		playbook.WithPlaybookOptions(ansiblePlaybookOptions),
+	)
 
-	fmt.Printf("\n  Ansible playbook command:\n%s\n\n", playbook.String())
+	fmt.Printf("\n  Ansible playbook command:\n%s\n\n", playbookCmd.String())
 
 	exec := configuration.NewAnsibleWithConfigurationSettingsExecute(
 		execute.NewDefaultExecute(
-			execute.WithCmd(playbook),
+			execute.WithCmd(playbookCmd),
 			execute.WithTransformers(
 				transformer.Prepend("Go-ansible example with become"),
 			),
diff --git a/examples/workflowexecute-ansibleplaybook-with-galaxy-install-collection/Makefile b/examples/workflowexecute-ansibleplaybook-with-galaxy-install-collection/Makefile
new file mode 100644
index 0000000..d06c0b1
--- /dev/null
+++ b/examples/workflowexecute-ansibleplaybook-with-galaxy-install-collection/Makefile
@@ -0,0 +1,50 @@
+
+DOCKER_COMPOSE_BINARY := $(shell docker compose version > /dev/null 2>&1 && echo "docker compose" || (which docker-compose > /dev/null 2>&1 && echo "docker-compose" || (echo "docker compose not found. Aborting." >&2; exit 1)))
+
+PROJECT_NAME := go-ansible-$(shell basename ${PWD})
+
+# dafault target
+.DEFAULT_GOAL: help
+
+help: ## Lists available targets
+	@echo
+	@echo "Makefile usage:"
+	@grep -E '^[a-zA-Z1-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "  \033[1;32m%-20s\033[0m %s\n", $$1, $$2}'
+	@echo
+
+build: ## Build the Docker compose environment
+	@$(DOCKER_COMPOSE_BINARY) build
+
+generate-keys: cleanup-keys ## Generate the SSH key pair required to autheneticate to SSH server
+	@$(DOCKER_COMPOSE_BINARY) --project-name $(PROJECT_NAME) run --rm openssh-client -t rsa -q -N "" -f id_rsa -C "apenella@$(PROJECT_NAME).test"
+
+cleanup-keys: ## Cleanup the SSH key pair
+	@$(DOCKER_COMPOSE_BINARY) --project-name $(PROJECT_NAME) run --rm --entrypoint /bin/sh openssh-client -c 'rm -rf $$(ls)'
+
+up: generate-keys ## Create and start containers
+	@$(DOCKER_COMPOSE_BINARY) --project-name $(PROJECT_NAME) up --detach --build
+
+down: ## Stop and remove containers, networks, and volumes
+	@$(DOCKER_COMPOSE_BINARY) --project-name $(PROJECT_NAME) down --volumes --remove-orphans --timeout 3
+
+restart: down up ## Restart the containers
+
+ps: ## List containers
+	@$(DOCKER_COMPOSE_BINARY) --project-name $(PROJECT_NAME) ps
+
+logs: ## Show all logs
+	@$(DOCKER_COMPOSE_BINARY) --project-name $(PROJECT_NAME) logs
+
+attach-ansible: ## Attach to the ansible container
+	@$(DOCKER_COMPOSE_BINARY) --project-name $(PROJECT_NAME) exec --workdir /code/examples/$$(basename $$(pwd)) ansible /bin/sh
+
+attach-server: ## Attach to the server container
+	@$(DOCKER_COMPOSE_BINARY) --project-name $(PROJECT_NAME) exec server /bin/sh
+
+project-name: ## Show the project name
+	@echo $(PROJECT_NAME)
+
+run: ## Run the example
+	@$(DOCKER_COMPOSE_BINARY) --project-name $(PROJECT_NAME) exec --workdir /code/examples/$$(basename $$(pwd)) ansible go run $$(basename $$(pwd)).go
+
+start-and-run: up run ## Start the environment and run the example
diff --git a/examples/workflowexecute-ansibleplaybook-with-galaxy-install-collection/docker-compose.yaml b/examples/workflowexecute-ansibleplaybook-with-galaxy-install-collection/docker-compose.yaml
new file mode 100644
index 0000000..6655a19
--- /dev/null
+++ b/examples/workflowexecute-ansibleplaybook-with-galaxy-install-collection/docker-compose.yaml
@@ -0,0 +1,41 @@
+name: ansibleplaybook-ssh
+
+services:
+  ansible:
+    build: 
+      context: docker/ansible
+    command: ["tail", "-f", "/dev/null"]
+    # command: ["ansible-playbook", "--help"]
+    volumes:
+      - ssh:/ssh
+      - ../..:/code
+    working_dir: /code
+    ## Set the init flag to true lets the process 1 to reap all the zombie processes
+    init: true
+    depends_on:
+      - server
+
+  server:
+    build: 
+      context: docker/server
+    # command: ["cat", "/etc/ssh/sshd_config"]
+    volumes:
+      - ssh:/ssh
+    depends_on:
+      - openssh-client
+    # ports:
+    #   - "22220:22"
+
+  openssh-client:
+    build: 
+      context: docker/openssh-client
+    working_dir: /ssh
+    volumes:
+      - ssh:/ssh
+    deploy:
+      resources:
+        limits:
+          memory: 10M
+
+volumes:
+  ssh:
\ No newline at end of file
diff --git a/examples/workflowexecute-ansibleplaybook-with-galaxy-install-collection/docker/ansible/Dockerfile b/examples/workflowexecute-ansibleplaybook-with-galaxy-install-collection/docker/ansible/Dockerfile
new file mode 100644
index 0000000..41c9850
--- /dev/null
+++ b/examples/workflowexecute-ansibleplaybook-with-galaxy-install-collection/docker/ansible/Dockerfile
@@ -0,0 +1,25 @@
+FROM python:3.12-alpine3.19
+
+RUN apk add --update --no-cache \
+        openssh-client \
+        git \
+    && rm -rf /var/cache/apk/*
+
+RUN pip3 install -U pip setuptools \
+    && pip3 install --no-cache-dir \
+        setuptools-rust \
+        cryptography \
+        # Required library to execute ansible community.general.dig plugin
+        dnspython \
+        ansible \
+    && ln /usr/local/bin/ansible-playbook /usr/bin/ansible-playbook
+
+RUN apk add --update --no-cache \
+        go
+
+# Configure Go
+ENV GOROOT /usr/lib/go
+ENV GOPATH /go
+ENV PATH /go/bin:$PATH
+
+RUN mkdir -p ${GOPATH}/src ${GOPATH}/bin
diff --git a/examples/workflowexecute-ansibleplaybook-with-galaxy-install-collection/docker/openssh-client/Dockerfile b/examples/workflowexecute-ansibleplaybook-with-galaxy-install-collection/docker/openssh-client/Dockerfile
new file mode 100644
index 0000000..4736669
--- /dev/null
+++ b/examples/workflowexecute-ansibleplaybook-with-galaxy-install-collection/docker/openssh-client/Dockerfile
@@ -0,0 +1,9 @@
+FROM alpine:3.19
+
+RUN apk add --update --no-cache \
+        openssh-client \
+        git \
+    && rm -rf /var/cache/apk/*
+
+ENTRYPOINT [ "/usr/bin/ssh-keygen" ]
+CMD [ "--help" ]
\ No newline at end of file
diff --git a/examples/workflowexecute-ansibleplaybook-with-galaxy-install-collection/docker/server/Dockerfile b/examples/workflowexecute-ansibleplaybook-with-galaxy-install-collection/docker/server/Dockerfile
new file mode 100644
index 0000000..0ca7432
--- /dev/null
+++ b/examples/workflowexecute-ansibleplaybook-with-galaxy-install-collection/docker/server/Dockerfile
@@ -0,0 +1,20 @@
+FROM python:bookworm
+
+
+RUN adduser --disabled-password --gecos "" ansible
+
+RUN apt-get update \
+    && apt-get install -y \
+        openssh-server \
+    && rm -rf /var/lib/apt/lists/*
+
+RUN mkdir /var/run/sshd \
+    && ssh-keygen -A
+
+COPY entrypoint.sh /usr/local/bin/entrypoint.sh
+RUN chmod +x /usr/local/bin/entrypoint.sh
+
+# Expose the SSH port
+EXPOSE 22
+
+CMD ["/usr/local/bin/entrypoint.sh"]
diff --git a/examples/workflowexecute-ansibleplaybook-with-galaxy-install-collection/docker/server/entrypoint.sh b/examples/workflowexecute-ansibleplaybook-with-galaxy-install-collection/docker/server/entrypoint.sh
new file mode 100644
index 0000000..722dc2f
--- /dev/null
+++ b/examples/workflowexecute-ansibleplaybook-with-galaxy-install-collection/docker/server/entrypoint.sh
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+home=$(getent passwd "$(whoami)" |  cut -d: -f6)
+
+mkdir -p "${home}/.ssh"
+cat /ssh/id_rsa.pub > "${home}/.ssh/authorized_keys"
+
+/usr/sbin/sshd -D
\ No newline at end of file
diff --git a/examples/workflowexecute-ansibleplaybook-with-galaxy-install-collection/inventory.yml b/examples/workflowexecute-ansibleplaybook-with-galaxy-install-collection/inventory.yml
new file mode 100644
index 0000000..a186167
--- /dev/null
+++ b/examples/workflowexecute-ansibleplaybook-with-galaxy-install-collection/inventory.yml
@@ -0,0 +1,9 @@
+---
+
+all:
+  hosts:
+    server:
+      ansible_host: server
+      ansible_user: root
+      ansible_ssh_private_key_file: /ssh/id_rsa
+
diff --git a/examples/workflowexecute-ansibleplaybook-with-galaxy-install-collection/requirements.yml b/examples/workflowexecute-ansibleplaybook-with-galaxy-install-collection/requirements.yml
new file mode 100644
index 0000000..5c7755d
--- /dev/null
+++ b/examples/workflowexecute-ansibleplaybook-with-galaxy-install-collection/requirements.yml
@@ -0,0 +1,4 @@
+---
+
+collections:
+- name: community.general
\ No newline at end of file
diff --git a/examples/workflowexecute-ansibleplaybook-with-galaxy-install-collection/site.yml b/examples/workflowexecute-ansibleplaybook-with-galaxy-install-collection/site.yml
new file mode 100644
index 0000000..41ac9dc
--- /dev/null
+++ b/examples/workflowexecute-ansibleplaybook-with-galaxy-install-collection/site.yml
@@ -0,0 +1,9 @@
+---
+
+- hosts: server
+  
+  tasks:
+
+  - name: Check version of community.general
+    ansible.builtin.debug:
+      msg: "community.general version {{ lookup('community.general.collection_version', 'community.general') }}"
diff --git a/examples/workflowexecute-ansibleplaybook-with-galaxy-install-collection/workflowexecute-ansibleplaybook-with-galaxy-install-collection.go b/examples/workflowexecute-ansibleplaybook-with-galaxy-install-collection/workflowexecute-ansibleplaybook-with-galaxy-install-collection.go
new file mode 100644
index 0000000..a3a48b9
--- /dev/null
+++ b/examples/workflowexecute-ansibleplaybook-with-galaxy-install-collection/workflowexecute-ansibleplaybook-with-galaxy-install-collection.go
@@ -0,0 +1,43 @@
+package main
+
+import (
+	"context"
+	"fmt"
+	"os"
+
+	"github.com/apenella/go-ansible/v2/pkg/execute"
+	"github.com/apenella/go-ansible/v2/pkg/execute/workflow"
+	galaxy "github.com/apenella/go-ansible/v2/pkg/galaxy/collection/install"
+	"github.com/apenella/go-ansible/v2/pkg/playbook"
+)
+
+func main() {
+
+	ansiblePlaybookOptions := &playbook.AnsiblePlaybookOptions{
+		Inventory:     "inventory.yml",
+		SSHCommonArgs: "-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null",
+	}
+
+	galaxyInstallCollectionCmd := galaxy.NewAnsibleGalaxyCollectionInstallCmd(
+		galaxy.WithGalaxyCollectionInstallOptions(&galaxy.AnsibleGalaxyCollectionInstallOptions{
+			Force:            true,
+			Upgrade:          true,
+			RequirementsFile: "requirements.yml",
+		}),
+	)
+
+	galaxyInstallCollectionExec := execute.NewDefaultExecute(
+		execute.WithCmd(galaxyInstallCollectionCmd),
+	)
+
+	playbookCmd := playbook.NewAnsiblePlaybookExecute("site.yml").
+		WithPlaybookOptions(ansiblePlaybookOptions)
+
+	err := workflow.NewWorkflowExecute(galaxyInstallCollectionExec, playbookCmd).
+		WithTrace().
+		Execute(context.TODO())
+	if err != nil {
+		fmt.Println(err.Error())
+		os.Exit(1)
+	}
+}
diff --git a/examples/workflowexecute-ansibleplaybook-with-galaxy-install-role/Makefile b/examples/workflowexecute-ansibleplaybook-with-galaxy-install-role/Makefile
new file mode 100644
index 0000000..d06c0b1
--- /dev/null
+++ b/examples/workflowexecute-ansibleplaybook-with-galaxy-install-role/Makefile
@@ -0,0 +1,50 @@
+
+DOCKER_COMPOSE_BINARY := $(shell docker compose version > /dev/null 2>&1 && echo "docker compose" || (which docker-compose > /dev/null 2>&1 && echo "docker-compose" || (echo "docker compose not found. Aborting." >&2; exit 1)))
+
+PROJECT_NAME := go-ansible-$(shell basename ${PWD})
+
+# dafault target
+.DEFAULT_GOAL: help
+
+help: ## Lists available targets
+	@echo
+	@echo "Makefile usage:"
+	@grep -E '^[a-zA-Z1-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "  \033[1;32m%-20s\033[0m %s\n", $$1, $$2}'
+	@echo
+
+build: ## Build the Docker compose environment
+	@$(DOCKER_COMPOSE_BINARY) build
+
+generate-keys: cleanup-keys ## Generate the SSH key pair required to autheneticate to SSH server
+	@$(DOCKER_COMPOSE_BINARY) --project-name $(PROJECT_NAME) run --rm openssh-client -t rsa -q -N "" -f id_rsa -C "apenella@$(PROJECT_NAME).test"
+
+cleanup-keys: ## Cleanup the SSH key pair
+	@$(DOCKER_COMPOSE_BINARY) --project-name $(PROJECT_NAME) run --rm --entrypoint /bin/sh openssh-client -c 'rm -rf $$(ls)'
+
+up: generate-keys ## Create and start containers
+	@$(DOCKER_COMPOSE_BINARY) --project-name $(PROJECT_NAME) up --detach --build
+
+down: ## Stop and remove containers, networks, and volumes
+	@$(DOCKER_COMPOSE_BINARY) --project-name $(PROJECT_NAME) down --volumes --remove-orphans --timeout 3
+
+restart: down up ## Restart the containers
+
+ps: ## List containers
+	@$(DOCKER_COMPOSE_BINARY) --project-name $(PROJECT_NAME) ps
+
+logs: ## Show all logs
+	@$(DOCKER_COMPOSE_BINARY) --project-name $(PROJECT_NAME) logs
+
+attach-ansible: ## Attach to the ansible container
+	@$(DOCKER_COMPOSE_BINARY) --project-name $(PROJECT_NAME) exec --workdir /code/examples/$$(basename $$(pwd)) ansible /bin/sh
+
+attach-server: ## Attach to the server container
+	@$(DOCKER_COMPOSE_BINARY) --project-name $(PROJECT_NAME) exec server /bin/sh
+
+project-name: ## Show the project name
+	@echo $(PROJECT_NAME)
+
+run: ## Run the example
+	@$(DOCKER_COMPOSE_BINARY) --project-name $(PROJECT_NAME) exec --workdir /code/examples/$$(basename $$(pwd)) ansible go run $$(basename $$(pwd)).go
+
+start-and-run: up run ## Start the environment and run the example
diff --git a/examples/workflowexecute-ansibleplaybook-with-galaxy-install-role/docker-compose.yaml b/examples/workflowexecute-ansibleplaybook-with-galaxy-install-role/docker-compose.yaml
new file mode 100644
index 0000000..6655a19
--- /dev/null
+++ b/examples/workflowexecute-ansibleplaybook-with-galaxy-install-role/docker-compose.yaml
@@ -0,0 +1,41 @@
+name: ansibleplaybook-ssh
+
+services:
+  ansible:
+    build: 
+      context: docker/ansible
+    command: ["tail", "-f", "/dev/null"]
+    # command: ["ansible-playbook", "--help"]
+    volumes:
+      - ssh:/ssh
+      - ../..:/code
+    working_dir: /code
+    ## Set the init flag to true lets the process 1 to reap all the zombie processes
+    init: true
+    depends_on:
+      - server
+
+  server:
+    build: 
+      context: docker/server
+    # command: ["cat", "/etc/ssh/sshd_config"]
+    volumes:
+      - ssh:/ssh
+    depends_on:
+      - openssh-client
+    # ports:
+    #   - "22220:22"
+
+  openssh-client:
+    build: 
+      context: docker/openssh-client
+    working_dir: /ssh
+    volumes:
+      - ssh:/ssh
+    deploy:
+      resources:
+        limits:
+          memory: 10M
+
+volumes:
+  ssh:
\ No newline at end of file
diff --git a/examples/workflowexecute-ansibleplaybook-with-galaxy-install-role/docker/ansible/Dockerfile b/examples/workflowexecute-ansibleplaybook-with-galaxy-install-role/docker/ansible/Dockerfile
new file mode 100644
index 0000000..41c9850
--- /dev/null
+++ b/examples/workflowexecute-ansibleplaybook-with-galaxy-install-role/docker/ansible/Dockerfile
@@ -0,0 +1,25 @@
+FROM python:3.12-alpine3.19
+
+RUN apk add --update --no-cache \
+        openssh-client \
+        git \
+    && rm -rf /var/cache/apk/*
+
+RUN pip3 install -U pip setuptools \
+    && pip3 install --no-cache-dir \
+        setuptools-rust \
+        cryptography \
+        # Required library to execute ansible community.general.dig plugin
+        dnspython \
+        ansible \
+    && ln /usr/local/bin/ansible-playbook /usr/bin/ansible-playbook
+
+RUN apk add --update --no-cache \
+        go
+
+# Configure Go
+ENV GOROOT /usr/lib/go
+ENV GOPATH /go
+ENV PATH /go/bin:$PATH
+
+RUN mkdir -p ${GOPATH}/src ${GOPATH}/bin
diff --git a/examples/workflowexecute-ansibleplaybook-with-galaxy-install-role/docker/openssh-client/Dockerfile b/examples/workflowexecute-ansibleplaybook-with-galaxy-install-role/docker/openssh-client/Dockerfile
new file mode 100644
index 0000000..4736669
--- /dev/null
+++ b/examples/workflowexecute-ansibleplaybook-with-galaxy-install-role/docker/openssh-client/Dockerfile
@@ -0,0 +1,9 @@
+FROM alpine:3.19
+
+RUN apk add --update --no-cache \
+        openssh-client \
+        git \
+    && rm -rf /var/cache/apk/*
+
+ENTRYPOINT [ "/usr/bin/ssh-keygen" ]
+CMD [ "--help" ]
\ No newline at end of file
diff --git a/examples/workflowexecute-ansibleplaybook-with-galaxy-install-role/docker/server/Dockerfile b/examples/workflowexecute-ansibleplaybook-with-galaxy-install-role/docker/server/Dockerfile
new file mode 100644
index 0000000..0ca7432
--- /dev/null
+++ b/examples/workflowexecute-ansibleplaybook-with-galaxy-install-role/docker/server/Dockerfile
@@ -0,0 +1,20 @@
+FROM python:bookworm
+
+
+RUN adduser --disabled-password --gecos "" ansible
+
+RUN apt-get update \
+    && apt-get install -y \
+        openssh-server \
+    && rm -rf /var/lib/apt/lists/*
+
+RUN mkdir /var/run/sshd \
+    && ssh-keygen -A
+
+COPY entrypoint.sh /usr/local/bin/entrypoint.sh
+RUN chmod +x /usr/local/bin/entrypoint.sh
+
+# Expose the SSH port
+EXPOSE 22
+
+CMD ["/usr/local/bin/entrypoint.sh"]
diff --git a/examples/workflowexecute-ansibleplaybook-with-galaxy-install-role/docker/server/entrypoint.sh b/examples/workflowexecute-ansibleplaybook-with-galaxy-install-role/docker/server/entrypoint.sh
new file mode 100644
index 0000000..722dc2f
--- /dev/null
+++ b/examples/workflowexecute-ansibleplaybook-with-galaxy-install-role/docker/server/entrypoint.sh
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+home=$(getent passwd "$(whoami)" |  cut -d: -f6)
+
+mkdir -p "${home}/.ssh"
+cat /ssh/id_rsa.pub > "${home}/.ssh/authorized_keys"
+
+/usr/sbin/sshd -D
\ No newline at end of file
diff --git a/examples/workflowexecute-ansibleplaybook-with-galaxy-install-role/inventory.yml b/examples/workflowexecute-ansibleplaybook-with-galaxy-install-role/inventory.yml
new file mode 100644
index 0000000..a186167
--- /dev/null
+++ b/examples/workflowexecute-ansibleplaybook-with-galaxy-install-role/inventory.yml
@@ -0,0 +1,9 @@
+---
+
+all:
+  hosts:
+    server:
+      ansible_host: server
+      ansible_user: root
+      ansible_ssh_private_key_file: /ssh/id_rsa
+
diff --git a/examples/workflowexecute-ansibleplaybook-with-galaxy-install-role/requirements.yml b/examples/workflowexecute-ansibleplaybook-with-galaxy-install-role/requirements.yml
new file mode 100644
index 0000000..def47b0
--- /dev/null
+++ b/examples/workflowexecute-ansibleplaybook-with-galaxy-install-role/requirements.yml
@@ -0,0 +1,5 @@
+---
+
+# ansible requirements.yml file include role go to install
+- src: geerlingguy.go
+  version: 1.1.0
\ No newline at end of file
diff --git a/examples/workflowexecute-ansibleplaybook-with-galaxy-install-role/site.yml b/examples/workflowexecute-ansibleplaybook-with-galaxy-install-role/site.yml
new file mode 100644
index 0000000..3955d55
--- /dev/null
+++ b/examples/workflowexecute-ansibleplaybook-with-galaxy-install-role/site.yml
@@ -0,0 +1,8 @@
+---
+
+- hosts: server
+  
+  tasks:
+    - name: Install nginx
+      ansible.builtin.import_role:
+        name: geerlingguy.go
diff --git a/examples/workflowexecute-ansibleplaybook-with-galaxy-install-role/workflowexecute-ansibleplaybook-with-galaxy-install-role.go b/examples/workflowexecute-ansibleplaybook-with-galaxy-install-role/workflowexecute-ansibleplaybook-with-galaxy-install-role.go
new file mode 100644
index 0000000..5785988
--- /dev/null
+++ b/examples/workflowexecute-ansibleplaybook-with-galaxy-install-role/workflowexecute-ansibleplaybook-with-galaxy-install-role.go
@@ -0,0 +1,43 @@
+package main
+
+import (
+	"context"
+	"fmt"
+	"os"
+
+	"github.com/apenella/go-ansible/v2/pkg/execute"
+	"github.com/apenella/go-ansible/v2/pkg/execute/workflow"
+	galaxy "github.com/apenella/go-ansible/v2/pkg/galaxy/role/install"
+	"github.com/apenella/go-ansible/v2/pkg/playbook"
+)
+
+func main() {
+
+	ansiblePlaybookOptions := &playbook.AnsiblePlaybookOptions{
+		Inventory:     "inventory.yml",
+		SSHCommonArgs: "-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null",
+	}
+
+	galaxyInstallRolesCmd := galaxy.NewAnsibleGalaxyRoleInstallCmd(
+		// galaxy.WithRoleNames("geerlingguy.go"),
+		galaxy.WithGalaxyRoleInstallOptions(&galaxy.AnsibleGalaxyRoleInstallOptions{
+			Force:    true,
+			RoleFile: "requirements.yml",
+		}),
+	)
+
+	galaxyInstallRolesExec := execute.NewDefaultExecute(
+		execute.WithCmd(galaxyInstallRolesCmd),
+	)
+
+	playbookCmd := playbook.NewAnsiblePlaybookExecute("site.yml").
+		WithPlaybookOptions(ansiblePlaybookOptions)
+
+	err := workflow.NewWorkflowExecute(galaxyInstallRolesExec, playbookCmd).
+		WithTrace().
+		Execute(context.TODO())
+	if err != nil {
+		fmt.Println(err.Error())
+		os.Exit(1)
+	}
+}
diff --git a/pkg/adhoc/ansibleAdhocCmd.go b/pkg/adhoc/ansibleAdhocCmd.go
index 4521fd0..ee2074e 100644
--- a/pkg/adhoc/ansibleAdhocCmd.go
+++ b/pkg/adhoc/ansibleAdhocCmd.go
@@ -12,6 +12,9 @@ const (
 	DefaultAnsibleAdhocBinary = "ansible"
 )
 
+// AnsibleAdhocOptionsFunc is a function to set executor options
+type AnsibleAdhocOptionsFunc func(*AnsibleAdhocCmd)
+
 // AnsibleAdhocCmd object is the main object which defines the `ansible` adhoc command and how to execute it.
 type AnsibleAdhocCmd struct {
 	// Ansible binary file
@@ -22,6 +25,38 @@ type AnsibleAdhocCmd struct {
 	AdhocOptions *AnsibleAdhocOptions
 }
 
+// NewAnsibleAdhocCmd creates a new AnsibleAdhocCmd instance
+func NewAnsibleAdhocCmd(options ...AnsibleAdhocOptionsFunc) *AnsibleAdhocCmd {
+	cmd := &AnsibleAdhocCmd{}
+
+	for _, option := range options {
+		option(cmd)
+	}
+
+	return cmd
+}
+
+// WithBinary set the ansible binary file
+func WithBinary(binary string) AnsibleAdhocOptionsFunc {
+	return func(a *AnsibleAdhocCmd) {
+		a.Binary = binary
+	}
+}
+
+// WithPattern set the adhoc pattern
+func WithPattern(pattern string) AnsibleAdhocOptionsFunc {
+	return func(a *AnsibleAdhocCmd) {
+		a.Pattern = pattern
+	}
+}
+
+// WithAdhocOptions set the adhoc options
+func WithAdhocOptions(options *AnsibleAdhocOptions) AnsibleAdhocOptionsFunc {
+	return func(a *AnsibleAdhocCmd) {
+		a.AdhocOptions = options
+	}
+}
+
 // Command generate the ansible command which will be executed
 func (a *AnsibleAdhocCmd) Command() ([]string, error) {
 	cmd := []string{}
diff --git a/pkg/adhoc/ansibleAdhocOptions.go b/pkg/adhoc/ansibleAdhocOptions.go
index a3eb4e0..e95d8d3 100644
--- a/pkg/adhoc/ansibleAdhocOptions.go
+++ b/pkg/adhoc/ansibleAdhocOptions.go
@@ -429,22 +429,22 @@ func (o *AnsibleAdhocOptions) GenerateAnsibleAdhocOptions() ([]string, error) {
 
 	if o.SCPExtraArgs != "" {
 		cmd = append(cmd, SCPExtraArgsFlag)
-		cmd = append(cmd, fmt.Sprintf("'%s'", o.SCPExtraArgs))
+		cmd = append(cmd, o.SCPExtraArgs)
 	}
 
 	if o.SFTPExtraArgs != "" {
 		cmd = append(cmd, SFTPExtraArgsFlag)
-		cmd = append(cmd, fmt.Sprintf("'%s'", o.SFTPExtraArgs))
+		cmd = append(cmd, o.SFTPExtraArgs)
 	}
 
 	if o.SSHCommonArgs != "" {
 		cmd = append(cmd, SSHCommonArgsFlag)
-		cmd = append(cmd, fmt.Sprintf("'%s'", o.SSHCommonArgs))
+		cmd = append(cmd, o.SSHCommonArgs)
 	}
 
 	if o.SSHExtraArgs != "" {
 		cmd = append(cmd, SSHExtraArgsFlag)
-		cmd = append(cmd, fmt.Sprintf("'%s'", o.SSHExtraArgs))
+		cmd = append(cmd, o.SSHExtraArgs)
 	}
 
 	if o.Timeout > 0 {
diff --git a/pkg/execute/workflow/workflowExecute.go b/pkg/execute/workflow/workflowExecute.go
index c984b72..3e477fc 100644
--- a/pkg/execute/workflow/workflowExecute.go
+++ b/pkg/execute/workflow/workflowExecute.go
@@ -5,6 +5,7 @@ import (
 	"fmt"
 
 	"github.com/apenella/go-ansible/v2/pkg/execute"
+	"github.com/fatih/color"
 )
 
 type WorkflowExecute struct {
@@ -12,6 +13,8 @@ type WorkflowExecute struct {
 	ExecutorList []execute.Executor
 	// ContinueOnError is a flag to continue on error
 	ContinueOnError bool
+	// Trace is a flag to trace the execution
+	Trace bool
 }
 
 // NewWorkflowExecute creates a new WorkflowExecute
@@ -33,11 +36,22 @@ func (e *WorkflowExecute) WithContinueOnError() *WorkflowExecute {
 	return e
 }
 
+// WithTrace sets the trace flag to true
+func (e *WorkflowExecute) WithTrace() *WorkflowExecute {
+	e.Trace = true
+	return e
+}
+
 // Execute runs the executors
 func (e *WorkflowExecute) Execute(ctx context.Context) error {
 	var errList []error = make([]error, 0)
 
-	for _, executor := range e.ExecutorList {
+	for executionNum, executor := range e.ExecutorList {
+
+		if e.Trace {
+			color.Blue(fmt.Sprintf("\n\u2022 executing task %d out of %d\n", executionNum+1, len(e.ExecutorList)))
+		}
+
 		err := executor.Execute(ctx)
 		if err != nil {
 			errList = append(errList, err)
diff --git a/pkg/galaxy/collection/collection.go b/pkg/galaxy/collection/collection.go
new file mode 100644
index 0000000..46019af
--- /dev/null
+++ b/pkg/galaxy/collection/collection.go
@@ -0,0 +1,4 @@
+package galaxycollection
+
+// AnsibleGalaxyCollectionSubCommand is the ansible-galaxy collection subcommand
+const AnsibleGalaxyCollectionSubCommand = "collection"
diff --git a/pkg/galaxy/collection/install/ansibleGalaxyCollectionInstallCmd.go b/pkg/galaxy/collection/install/ansibleGalaxyCollectionInstallCmd.go
new file mode 100644
index 0000000..8f880df
--- /dev/null
+++ b/pkg/galaxy/collection/install/ansibleGalaxyCollectionInstallCmd.go
@@ -0,0 +1,108 @@
+package galaxycollectioninstall
+
+import (
+	"fmt"
+
+	galaxy "github.com/apenella/go-ansible/v2/pkg/galaxy"
+	galaxycollection "github.com/apenella/go-ansible/v2/pkg/galaxy/collection"
+)
+
+const (
+	// AnsibleGalaxyCollectionInstallSubCommand is the ansible-galaxy collection install subcommand
+	AnsibleGalaxyCollectionInstallSubCommand = "install"
+)
+
+// AnsibleGalaxyCollectionInstallOptionsFunc is a function to set executor options
+type AnsibleGalaxyCollectionInstallOptionsFunc func(*AnsibleGalaxyCollectionInstallCmd)
+
+// AnsibleGalaxyCollectionInstallCmd object is the main object which defines the `ansible-galaxy` command to install collections.
+type AnsibleGalaxyCollectionInstallCmd struct {
+	// Binary is the ansible-galaxy binary file
+	Binary string
+
+	// CollectionNames is the ansible-galaxy's collection names to be installed
+	CollectionNames []string
+
+	// GalaxyCollectionInstallOptions are the ansible-galaxy's collection install options
+	GalaxyCollectionInstallOptions *AnsibleGalaxyCollectionInstallOptions
+}
+
+// NewAnsibleGalaxyCollectionInstallCmd creates a new AnsibleGalaxyCollectionInstallCmd instance
+func NewAnsibleGalaxyCollectionInstallCmd(options ...AnsibleGalaxyCollectionInstallOptionsFunc) *AnsibleGalaxyCollectionInstallCmd {
+	cmd := &AnsibleGalaxyCollectionInstallCmd{}
+
+	for _, option := range options {
+		option(cmd)
+	}
+
+	return cmd
+}
+
+// WithBinary set the ansible-galaxy binary file
+func WithBinary(binary string) AnsibleGalaxyCollectionInstallOptionsFunc {
+	return func(p *AnsibleGalaxyCollectionInstallCmd) {
+		p.Binary = binary
+	}
+}
+
+// WithGalaxyCollectionInstallOptions set the ansible-galaxy collection install options
+func WithGalaxyCollectionInstallOptions(options *AnsibleGalaxyCollectionInstallOptions) AnsibleGalaxyCollectionInstallOptionsFunc {
+	return func(p *AnsibleGalaxyCollectionInstallCmd) {
+		p.GalaxyCollectionInstallOptions = options
+	}
+}
+
+// WithCollectionNames set the ansible-galaxy role names
+func WithCollectionNames(roleNames ...string) AnsibleGalaxyCollectionInstallOptionsFunc {
+	return func(p *AnsibleGalaxyCollectionInstallCmd) {
+		p.CollectionNames = append([]string{}, roleNames...)
+	}
+}
+
+// Command generate the ansible-galaxy role install command which will be executed
+func (p *AnsibleGalaxyCollectionInstallCmd) Command() ([]string, error) {
+	cmd := []string{}
+
+	// Use default binary when it is not already defined
+	if p.Binary == "" {
+		p.Binary = galaxy.DefaultAnsibleGalaxyBinary
+	}
+
+	cmd = append(cmd, p.Binary, galaxycollection.AnsibleGalaxyCollectionSubCommand, AnsibleGalaxyCollectionInstallSubCommand)
+
+	// Add the options
+	if p.GalaxyCollectionInstallOptions != nil {
+		options, err := p.GalaxyCollectionInstallOptions.GenerateCommandOptions()
+		if err != nil {
+			return nil, err
+		}
+		cmd = append(cmd, options...)
+	}
+
+	// Add the role names
+	cmd = append(cmd, p.CollectionNames...)
+
+	return cmd, nil
+}
+
+// String returns the ansible-galaxy role install command as a string
+func (p *AnsibleGalaxyCollectionInstallCmd) String() string {
+
+	// Use default binary when it is not already defined
+	if p.Binary == "" {
+		p.Binary = galaxy.DefaultAnsibleGalaxyBinary
+	}
+
+	str := fmt.Sprintf("%s %s %s", p.Binary, galaxycollection.AnsibleGalaxyCollectionSubCommand, AnsibleGalaxyCollectionInstallSubCommand)
+
+	if p.GalaxyCollectionInstallOptions != nil {
+		str = fmt.Sprintf("%s %s", str, p.GalaxyCollectionInstallOptions.String())
+	}
+
+	// Include the role names
+	for _, roleName := range p.CollectionNames {
+		str = fmt.Sprintf("%s %s", str, roleName)
+	}
+
+	return str
+}
diff --git a/pkg/galaxy/collection/install/ansibleGalaxyCollectionInstallOptions.go b/pkg/galaxy/collection/install/ansibleGalaxyCollectionInstallOptions.go
new file mode 100644
index 0000000..c8ade2f
--- /dev/null
+++ b/pkg/galaxy/collection/install/ansibleGalaxyCollectionInstallOptions.go
@@ -0,0 +1,369 @@
+package galaxycollectioninstall
+
+import (
+	"fmt"
+	"strconv"
+	"strings"
+
+	errors "github.com/apenella/go-common-utils/error"
+)
+
+const (
+
+	// APIKeyFlag represent the API key to use to authenticate against the galaxy server. Same as --token
+	APIKeyFlag = "--api-key"
+
+	// ClearResponseCacheFlag clears the existing server response cache.
+	ClearResponseCacheFlag = "--clear-response-cache"
+
+	// CollectionsPathFlag is the path to the directory containing your collections.
+	CollectionsPathFlag = "--collections-path"
+
+	// DisableGPGVerifyFlag disables GPG signature verification when installing collections from a Galaxy server.
+	DisableGPGVerifyFlag = "--disable-gpg-verify"
+
+	// ForceFlag forces overwriting an existing role or collection.
+	ForceFlag = "--force"
+
+	// ForceWithDepsFlag forces overwriting an existing collection and its dependencies.
+	ForceWithDepsFlag = "--force-with-deps"
+
+	// IgnoreCertsFlag ignores SSL certificate validation errors.
+	IgnoreCertsFlag = "--ignore-certs"
+
+	// IgnoreErrorsFlag ignores errors during installation and continue with the next specified collection.
+	IgnoreErrorsFlag = "--ignore-errors"
+
+	// IgnoreSignatureStatusCodeFlag suppresses this argument. It may be specified multiple times.
+	IgnoreSignatureStatusCodeFlag = "--ignore-signature-status-code"
+
+	// IgnoreSignatureStatusCodesFlag is a space separated list of status codes to ignore during signature verification.
+	IgnoreSignatureStatusCodesFlag = "--ignore-signature-status-codes"
+
+	// KeyringFlag is the keyring used during signature verification.
+	KeyringFlag = "--keyring"
+
+	// NoCacheFlag does not use the server response cache.
+	NoCacheFlag = "--no-cache"
+
+	// NoDepsFlag doesn’t download collections listed as dependencies.
+	NoDepsFlag = "--no-deps"
+
+	// OfflineFlag installs collection artifacts (tarballs) without contacting any distribution servers.
+	OfflineFlag = "--offline"
+
+	// PreFlag includes pre-release versions. Semantic versioning pre-releases are ignored by default.
+	PreFlag = "--pre"
+
+	// RequiredValidSignatureCountFlag is the number of signatures that must successfully verify the collection.
+	RequiredValidSignatureCountFlag = "--required-valid-signature-count"
+
+	// RequirementsFileFlag is a file containing a list of collections to be installed.
+	RequirementsFileFlag = "--requirements-file"
+
+	// ServerFlag is the Galaxy API server URL.
+	ServerFlag = "--server"
+
+	// SignatureFlag is an additional signature source to verify the authenticity of the MANIFEST.json.
+	SignatureFlag = "--signature"
+
+	// TimeoutFlag is the time to wait for operations against the galaxy server, defaults to 60s.
+	TimeoutFlag = "--timeout"
+
+	// TokenFlag represent the token to use to authenticate against the galaxy server. Same as --api-key
+	TokenFlag = "--token"
+
+	// UpgradeFlag upgrades installed collection artifacts. This will also update dependencies unless –no-deps is provided.
+	UpgradeFlag = "--upgrade"
+
+	// VerboseFlag verbose mode enabled
+	VerboseFlag = "--verbose"
+
+	// VersionFlag show program's version number, config file location, configured module search path, module location, executable location and exit
+	VersionFlag = "--version"
+)
+
+type AnsibleGalaxyCollectionInstallOptions struct {
+
+	// APIKey is the Ansible Galaxy API key.
+	APIKey string
+
+	// ClearResponseCache clears the existing server response cache.
+	ClearResponseCache bool
+
+	// DisableGPGVerify disables GPG signature verification when installing collections from a Galaxy server.
+	DisableGPGVerify bool
+
+	// ForceWithDeps forces overwriting an existing collection and its dependencies.
+	ForceWithDeps bool
+
+	// IgnoreSignatureStatusCode suppresses this argument. It may be specified multiple times.
+	IgnoreSignatureStatusCode bool
+
+	// IgnoreSignatureStatusCodes is a space separated list of status codes to ignore during signature verification.
+	IgnoreSignatureStatusCodes string
+
+	// Keyring is the keyring used during signature verification.
+	Keyring string
+
+	// NoCache does not use the server response cache.
+	NoCache bool
+
+	// Offline installs collection artifacts (tarballs) without contacting any distribution servers.
+	Offline bool
+
+	// Pre includes pre-release versions. Semantic versioning pre-releases are ignored by default.
+	Pre bool
+
+	// RequiredValidSignatureCount is the number of signatures that must successfully verify the collection.
+	RequiredValidSignatureCount int
+
+	// Signature is an additional signature source to verify the authenticity of the MANIFEST.json.
+	Signature string
+
+	// Timeout is the time to wait for operations against the galaxy server, defaults to 60s.
+	Timeout string
+
+	// Token is the Ansible Galaxy API key.
+	Token string
+
+	// Upgrade upgrades installed collection artifacts. This will also update dependencies unless –no-deps is provided.
+	Upgrade bool
+
+	// IgnoreCerts ignores SSL certificate validation errors.
+	IgnoreCerts bool
+
+	// Force forces overwriting an existing role or collection.
+	Force bool
+
+	// IgnoreErrors ignores errors during installation and continue with the next specified collection.
+	IgnoreErrors bool
+
+	// NoDeps doesn’t download collections listed as dependencies.
+	NoDeps bool
+
+	// CollectionsPath is the path to the directory containing your collections.
+	CollectionsPath string
+
+	// RequirementsFile is a file containing a list of collections to be installed.
+	RequirementsFile string
+
+	// Server is the Galaxy API server URL.
+	Server string
+
+	// Verbose verbose mode enabled
+	Verbose bool
+
+	// Version show program's version number, config file location, configured module search path, module location, executable location and exit
+	Version bool
+}
+
+func (o *AnsibleGalaxyCollectionInstallOptions) GenerateCommandOptions() ([]string, error) {
+	errContext := "(galaxy::AnsibleGalaxyCollectionInstallOptions::GenerateCommandOptions)"
+	options := []string{}
+
+	if o == nil {
+		return nil, errors.New(errContext, "AnsibleGalaxyCollectionInstallOptions is nil")
+	}
+
+	if o.APIKey != "" {
+		options = append(options, APIKeyFlag, o.APIKey)
+	}
+
+	if o.ClearResponseCache {
+		options = append(options, ClearResponseCacheFlag)
+	}
+
+	if o.DisableGPGVerify {
+		options = append(options, DisableGPGVerifyFlag)
+	}
+
+	if o.ForceWithDeps {
+		options = append(options, ForceWithDepsFlag)
+	}
+
+	if o.IgnoreSignatureStatusCode {
+		options = append(options, IgnoreSignatureStatusCodeFlag)
+	}
+
+	if o.IgnoreSignatureStatusCodes != "" {
+		options = append(options, IgnoreSignatureStatusCodesFlag, o.IgnoreSignatureStatusCodes)
+	}
+
+	if o.Keyring != "" {
+		options = append(options, KeyringFlag, o.Keyring)
+	}
+
+	if o.NoCache {
+		options = append(options, NoCacheFlag)
+	}
+
+	if o.Offline {
+		options = append(options, OfflineFlag)
+	}
+
+	if o.Pre {
+		options = append(options, PreFlag)
+	}
+
+	if o.RequiredValidSignatureCount > 0 {
+		options = append(options, RequiredValidSignatureCountFlag, strconv.Itoa(o.RequiredValidSignatureCount))
+	}
+
+	if o.Signature != "" {
+		options = append(options, SignatureFlag, o.Signature)
+	}
+
+	if o.Timeout != "" {
+		options = append(options, TimeoutFlag, o.Timeout)
+	}
+
+	if o.Token != "" {
+		options = append(options, TokenFlag, o.Token)
+	}
+
+	if o.Upgrade {
+		options = append(options, UpgradeFlag)
+	}
+
+	if o.IgnoreCerts {
+		options = append(options, IgnoreCertsFlag)
+	}
+
+	if o.Force {
+		options = append(options, ForceFlag)
+	}
+
+	if o.IgnoreErrors {
+		options = append(options, IgnoreErrorsFlag)
+	}
+
+	if o.NoDeps {
+		options = append(options, NoDepsFlag)
+	}
+
+	if o.CollectionsPath != "" {
+		options = append(options, CollectionsPathFlag, o.CollectionsPath)
+	}
+
+	if o.RequirementsFile != "" {
+		options = append(options, RequirementsFileFlag, o.RequirementsFile)
+	}
+
+	if o.Server != "" {
+		options = append(options, ServerFlag, o.Server)
+	}
+
+	if o.Verbose {
+		options = append(options, VerboseFlag)
+	}
+
+	if o.Version {
+		options = append(options, VersionFlag)
+	}
+
+	return options, nil
+}
+
+// String return a string representation of the AnsibleGalaxyCollectionInstallOptions
+func (o *AnsibleGalaxyCollectionInstallOptions) String() string {
+	str := ""
+
+	if o.APIKey != "" {
+		str = fmt.Sprintf("%s %s %s", str, APIKeyFlag, o.APIKey)
+	}
+
+	if o.ClearResponseCache {
+		str = fmt.Sprintf("%s %s", str, ClearResponseCacheFlag)
+	}
+
+	if o.DisableGPGVerify {
+		str = fmt.Sprintf("%s %s", str, DisableGPGVerifyFlag)
+	}
+
+	if o.ForceWithDeps {
+		str = fmt.Sprintf("%s %s", str, ForceWithDepsFlag)
+	}
+
+	if o.IgnoreSignatureStatusCode {
+		str = fmt.Sprintf("%s %s", str, IgnoreSignatureStatusCodeFlag)
+	}
+
+	if o.IgnoreSignatureStatusCodes != "" {
+		str = fmt.Sprintf("%s %s %s", str, IgnoreSignatureStatusCodesFlag, o.IgnoreSignatureStatusCodes)
+	}
+
+	if o.Keyring != "" {
+		str = fmt.Sprintf("%s %s %s", str, KeyringFlag, o.Keyring)
+	}
+
+	if o.NoCache {
+		str = fmt.Sprintf("%s %s", str, NoCacheFlag)
+	}
+
+	if o.Offline {
+		str = fmt.Sprintf("%s %s", str, OfflineFlag)
+	}
+
+	if o.Pre {
+		str = fmt.Sprintf("%s %s", str, PreFlag)
+	}
+
+	if o.RequiredValidSignatureCount > 0 {
+		str = fmt.Sprintf("%s %s %d", str, RequiredValidSignatureCountFlag, o.RequiredValidSignatureCount)
+	}
+
+	if o.Signature != "" {
+		str = fmt.Sprintf("%s %s %s", str, SignatureFlag, o.Signature)
+	}
+
+	if o.Timeout != "" {
+		str = fmt.Sprintf("%s %s %s", str, TimeoutFlag, o.Timeout)
+	}
+
+	if o.Token != "" {
+		str = fmt.Sprintf("%s %s %s", str, TokenFlag, o.Token)
+	}
+
+	if o.Upgrade {
+		str = fmt.Sprintf("%s %s", str, UpgradeFlag)
+	}
+
+	if o.IgnoreCerts {
+		str = fmt.Sprintf("%s %s", str, IgnoreCertsFlag)
+	}
+
+	if o.Force {
+		str = fmt.Sprintf("%s %s", str, ForceFlag)
+	}
+
+	if o.IgnoreErrors {
+		str = fmt.Sprintf("%s %s", str, IgnoreErrorsFlag)
+	}
+
+	if o.NoDeps {
+		str = fmt.Sprintf("%s %s", str, NoDepsFlag)
+	}
+
+	if o.CollectionsPath != "" {
+		str = fmt.Sprintf("%s %s %s", str, CollectionsPathFlag, o.CollectionsPath)
+	}
+
+	if o.RequirementsFile != "" {
+		str = fmt.Sprintf("%s %s %s", str, RequirementsFileFlag, o.RequirementsFile)
+	}
+
+	if o.Server != "" {
+		str = fmt.Sprintf("%s %s %s", str, ServerFlag, o.Server)
+	}
+
+	if o.Verbose {
+		str = fmt.Sprintf("%s %s", str, VerboseFlag)
+	}
+
+	if o.Version {
+		str = fmt.Sprintf("%s %s", str, VersionFlag)
+	}
+
+	return strings.TrimSpace(str)
+}
diff --git a/pkg/galaxy/galaxy.go b/pkg/galaxy/galaxy.go
new file mode 100644
index 0000000..eee7a25
--- /dev/null
+++ b/pkg/galaxy/galaxy.go
@@ -0,0 +1,4 @@
+package galaxy
+
+// DefaultAnsibleGalaxyBinary is the ansible-galaxy binary file default value
+const DefaultAnsibleGalaxyBinary = "ansible-galaxy"
diff --git a/pkg/galaxy/role/install/ansibleGalaxyRoleInstallCmd.go b/pkg/galaxy/role/install/ansibleGalaxyRoleInstallCmd.go
new file mode 100644
index 0000000..c13292f
--- /dev/null
+++ b/pkg/galaxy/role/install/ansibleGalaxyRoleInstallCmd.go
@@ -0,0 +1,114 @@
+package galaxyroleinstall
+
+import (
+	"fmt"
+
+	galaxy "github.com/apenella/go-ansible/v2/pkg/galaxy"
+	galaxyrole "github.com/apenella/go-ansible/v2/pkg/galaxy/role"
+)
+
+const (
+	// // DefaultAnsibleGalaxyRoleInstallBinary is the ansible-galaxy binary file default value
+	// DefaultAnsibleGalaxyRoleInstallBinary = "ansible-galaxy"
+
+	// // AnsibleGalaxyRoleSubCommand is the ansible-galaxy role subcommand
+	// AnsibleGalaxyRoleSubCommand = "role"
+
+	// AnsibleGalaxyRoleInstallSubCommand is the ansible-galaxy role install subcommand
+	AnsibleGalaxyRoleInstallSubCommand = "install"
+)
+
+// AnsibleGalaxyRoleInstallOptionsFunc is a function to set executor options
+type AnsibleGalaxyRoleInstallOptionsFunc func(*AnsibleGalaxyRoleInstallCmd)
+
+// AnsibleGalaxyRoleInstallCmd object is the main object which defines the `ansible-galaxy` command to install roles.
+type AnsibleGalaxyRoleInstallCmd struct {
+	// Binary is the ansible-galaxy binary file
+	Binary string
+
+	// RoleNames is the ansible-galaxy's role names to be installed
+	RoleNames []string
+
+	// GalaxyRoleInstallOptions are the ansible-galaxy's role install options
+	GalaxyRoleInstallOptions *AnsibleGalaxyRoleInstallOptions
+}
+
+// NewAnsibleGalaxyRoleInstallCmd creates a new AnsibleGalaxyRoleInstallCmd instance
+func NewAnsibleGalaxyRoleInstallCmd(options ...AnsibleGalaxyRoleInstallOptionsFunc) *AnsibleGalaxyRoleInstallCmd {
+	cmd := &AnsibleGalaxyRoleInstallCmd{}
+
+	for _, option := range options {
+		option(cmd)
+	}
+
+	return cmd
+}
+
+// WithBinary set the ansible-galaxy binary file
+func WithBinary(binary string) AnsibleGalaxyRoleInstallOptionsFunc {
+	return func(p *AnsibleGalaxyRoleInstallCmd) {
+		p.Binary = binary
+	}
+}
+
+// WithGalaxyRoleInstallOptions set the ansible-galaxy role install options
+func WithGalaxyRoleInstallOptions(options *AnsibleGalaxyRoleInstallOptions) AnsibleGalaxyRoleInstallOptionsFunc {
+	return func(p *AnsibleGalaxyRoleInstallCmd) {
+		p.GalaxyRoleInstallOptions = options
+	}
+}
+
+// WithRoleNames set the ansible-galaxy role names
+func WithRoleNames(roleNames ...string) AnsibleGalaxyRoleInstallOptionsFunc {
+	return func(p *AnsibleGalaxyRoleInstallCmd) {
+		p.RoleNames = append([]string{}, roleNames...)
+	}
+}
+
+// Command generate the ansible-galaxy role install command which will be executed
+func (p *AnsibleGalaxyRoleInstallCmd) Command() ([]string, error) {
+	cmd := []string{}
+
+	// Use default binary when it is not already defined
+	if p.Binary == "" {
+		p.Binary = galaxy.DefaultAnsibleGalaxyBinary
+	}
+
+	cmd = append(cmd, p.Binary, galaxyrole.AnsibleGalaxyRoleSubCommand, AnsibleGalaxyRoleInstallSubCommand)
+
+	// Add the options
+	if p.GalaxyRoleInstallOptions != nil {
+		options, err := p.GalaxyRoleInstallOptions.GenerateCommandOptions()
+		if err != nil {
+			return nil, err
+		}
+		cmd = append(cmd, options...)
+	}
+
+	// Add the role names
+	cmd = append(cmd, p.RoleNames...)
+
+	return cmd, nil
+}
+
+// String returns the ansible-galaxy role install command as a string
+func (p *AnsibleGalaxyRoleInstallCmd) String() string {
+
+	// Use default binary when it is not already defined
+	if p.Binary == "" {
+		p.Binary = galaxy.DefaultAnsibleGalaxyBinary
+	}
+
+	str := fmt.Sprintf("%s %s %s", p.Binary, galaxyrole.AnsibleGalaxyRoleSubCommand, AnsibleGalaxyRoleInstallSubCommand)
+
+	if p.GalaxyRoleInstallOptions != nil {
+		str = fmt.Sprintf("%s %s", str, p.GalaxyRoleInstallOptions.String())
+	}
+
+	// Include the role names
+	for _, roleName := range p.RoleNames {
+		str = fmt.Sprintf("%s %s", str, roleName)
+	}
+
+	return str
+}
diff --git a/pkg/galaxy/role/install/ansibleGalaxyRoleInstallOptions.go b/pkg/galaxy/role/install/ansibleGalaxyRoleInstallOptions.go
new file mode 100644
index 0000000..b9d92cf
--- /dev/null
+++ b/pkg/galaxy/role/install/ansibleGalaxyRoleInstallOptions.go
@@ -0,0 +1,300 @@
+package galaxyroleinstall
+
+import (
+	"fmt"
+
+	errors "github.com/apenella/go-common-utils/error"
+)
+
+const (
+
+	// APIKeyFlag represent the API key to use to authenticate against the galaxy server. Same as --token
+	APIKeyFlag = "--api-key"
+
+	// ForceFlag represents the command line flag for forcing overwriting an existing role or role file.
+	ForceFlag = "--force"
+
+	// ForceWithDepsFlag represents the command line flag for forcing overwriting an existing role, role file, or dependencies.
+	ForceWithDepsFlag = "--force-with-deps"
+
+	// IgnoreCertsFlag represent the flag to ignore SSL certificate validation errors
+	IgnoreCertsFlag = "--ignore-certs"
+
+	// IgnoreErrorsFlag represents the command line flag for continuing processing even if a role fails to install.
+	IgnoreErrorsFlag = "--ignore-errors"
+
+	// KeepSCMMetaFlag represent the flag to use tar instead of the scm archive option when packaging the role.
+	KeepSCMMetaFlag = "--keep-scm-meta"
+
+	// NoDepsFlag represents the command line flag for not installing dependencies.
+	NoDepsFlag = "--no-deps"
+
+	// RoleFileFlag represents the command line flag for specifying the path to a file containing a list of roles to install.
+	RoleFileFlag = "--role-file"
+
+	// RolesPathFlag represents the command line flag for specifying where roles should be installed on the local filesystem.
+	RolesPathFlag = "--roles-path"
+
+	// ServerFlag represent the flag to specify the galaxy server to use
+	ServerFlag = "--server"
+
+	// TimeoutFlag represent the time to wait for operations against the galaxy server, defaults to 60s
+	TimeoutFlag = "--timeout"
+
+	// TokenFlag represent the token to use to authenticate against the galaxy server. Same as --api-key
+	TokenFlag = "--token"
+
+	// VerboseFlag verbose mode enabled
+	VerboseFlag = "-vvvv"
+
+	// VerboseVFlag verbose with -v is enabled
+	VerboseVFlag = "-v"
+
+	// VerboseVVFlag verbose with -vv is enabled
+	VerboseVVFlag = "-vv"
+
+	// VerboseVVVFlag verbose with -vvv is enabled
+	VerboseVVVFlag = "-vvv"
+
+	// VerboseVVVVFlag verbose with -vvvv is enabled
+	VerboseVVVVFlag = "-vvvv"
+
+	// VersionFlag show program's version number, config file location, configured module search path, module location, executable location and exit
+	VersionFlag = "--version"
+)
+
+// AnsibleGalaxyRoleInstallOptions represents the options that can be passed to the ansible-galaxy role install command.
+type AnsibleGalaxyRoleInstallOptions struct {
+
+	// ApiKey represent the API key to use to authenticate against the galaxy server. Same as --token
+	ApiKey string
+
+	// Force represents whether to force overwriting an existing role or role file.
+	Force bool
+
+	// ForceWithDeps represents whether to force overwriting an existing role, role file, or dependencies.
+	ForceWithDeps bool
+
+	// IgnoreCerts represent the flag to ignore SSL certificate validation errors
+	IgnoreCerts bool
+
+	// IgnoreErrors represents whether to continue processing even if a role fails to install.
+	IgnoreErrors bool
+
+	// KeepSCMMeta represent the flag to use tar instead of the scm archive option when packaging the role.
+	KeepSCMMeta bool
+
+	// NoDeps represents whether to install dependencies.
+	NoDeps bool
+
+	// RoleFile represents the path to a file containing a list of roles to install.
+	RoleFile string
+
+	// RolesPath represents the path where roles should be installed on the local filesystem.
+	RolesPath string
+
+	// Server represent the flag to specify the galaxy server to use
+	Server string
+
+	// Timeout represent the time to wait for operations against the galaxy server, defaults to 60s
+	Timeout string
+
+	// Token represent the token to use to authenticate against the galaxy server. Same as --api-key
+	Token string
+
+	// Verbose verbose mode enabled
+	Verbose bool
+
+	// Verbose verbose mode -v enabled
+	VerboseV bool
+
+	// Verbose verbose mode -vv enabled
+	VerboseVV bool
+
+	// Verbose verbose mode -vvv enabled
+	VerboseVVV bool
+
+	// Verbose verbose mode -vvvv enabled
+	VerboseVVVV bool
+
+	// Version show program's version number, config file location, configured module search path, module location, executable location and exit
+	Version bool
+}
+
+// GenerateCommandOptions generates the command line options for the ansible-galaxy role install command.
+func (o *AnsibleGalaxyRoleInstallOptions) GenerateCommandOptions() ([]string, error) {
+
+	errContext := "(galaxy::AnsibleGalaxyRoleInstallOptions::GenerateCommandOptions)"
+	options := []string{}
+
+	if o == nil {
+		return nil, errors.New(errContext, "AnsibleGalaxyRoleInstallOptions is nil")
+	}
+
+	if o.ApiKey != "" {
+		options = append(options, APIKeyFlag, o.ApiKey)
+	}
+
+	if o.Force {
+		options = append(options, ForceFlag)
+	}
+
+	if o.ForceWithDeps {
+		options = append(options, ForceWithDepsFlag)
+	}
+
+	if o.IgnoreCerts {
+		options = append(options, IgnoreCertsFlag)
+	}
+
+	if o.IgnoreErrors {
+		options = append(options, IgnoreErrorsFlag)
+	}
+
+	if o.KeepSCMMeta {
+		options = append(options, KeepSCMMetaFlag)
+	}
+
+	if o.NoDeps {
+		options = append(options, NoDepsFlag)
+	}
+
+	if o.RoleFile != "" {
+		options = append(options, RoleFileFlag, o.RoleFile)
+	}
+
+	if o.RolesPath != "" {
+		options = append(options, RolesPathFlag, o.RolesPath)
+	}
+
+	if o.Server != "" {
+		options = append(options, ServerFlag, o.Server)
+	}
+
+	if o.Timeout != "" {
+		options = append(options, TimeoutFlag, o.Timeout)
+	}
+
+	if o.Token != "" {
+		options = append(options, TokenFlag, o.Token)
+	}
+
+	verboseFlag, err := o.generateVerbosityFlag()
+	if err != nil {
+		return nil, errors.New(errContext, "", err)
+	}
+
+	if verboseFlag != "" {
+		options = append(options, verboseFlag)
+	}
+
+	if o.Version {
+		options = append(options, VersionFlag)
+	}
+
+	return options, nil
+}
+
+// generateVerbosityFlag return a string with the verbose flag. Higher verbosity (more v's) has precedence over lower
+func (o *AnsibleGalaxyRoleInstallOptions) generateVerbosityFlag() (string, error) {
+	if o.Verbose {
+		return VerboseFlag, nil
+	}
+
+	if o.VerboseVVVV {
+		return VerboseVVVVFlag, nil
+	}
+
+	if o.VerboseVVV {
+		return VerboseVVVFlag, nil
+	}
+
+	if o.VerboseVV {
+		return VerboseVVFlag, nil
+	}
+
+	if o.VerboseV {
+		return VerboseVFlag, nil
+	}
+
+	return "", nil
+}
+
+// String returns a string representation of the ansible-galaxy role install options.
+func (o *AnsibleGalaxyRoleInstallOptions) String() string {
+	str := ""
+
+	if o.ApiKey != "" {
+		str = fmt.Sprintf("%s %s %s", str, APIKeyFlag, o.ApiKey)
+	}
+
+	if o.Force {
+		str = fmt.Sprintf("%s %s", str, ForceFlag)
+	}
+
+	if o.ForceWithDeps {
+		str = fmt.Sprintf("%s %s", str, ForceWithDepsFlag)
+	}
+
+	if o.IgnoreCerts {
+		str = fmt.Sprintf("%s %s", str, IgnoreCertsFlag)
+	}
+
+	if o.IgnoreErrors {
+		str = fmt.Sprintf("%s %s", str, IgnoreErrorsFlag)
+	}
+
+	if o.KeepSCMMeta {
+		str = fmt.Sprintf("%s %s", str, KeepSCMMetaFlag)
+	}
+
+	if o.NoDeps {
+		str = fmt.Sprintf("%s %s", str, NoDepsFlag)
+	}
+
+	if o.RoleFile != "" {
+		str = fmt.Sprintf("%s %s %s", str, RoleFileFlag, o.RoleFile)
+	}
+
+	if o.RolesPath != "" {
+		str = fmt.Sprintf("%s %s %s", str, RolesPathFlag, o.RolesPath)
+	}
+
+	if o.Server != "" {
+		str = fmt.Sprintf("%s %s %s", str, ServerFlag, o.Server)
+	}
+
+	if o.Timeout != "" {
+		str = fmt.Sprintf("%s %s %s", str, TimeoutFlag, o.Timeout)
+	}
+
+	if o.Token != "" {
+		str = fmt.Sprintf("%s %s %s", str, TokenFlag, o.Token)
+	}
+
+	if o.Verbose {
+		str = fmt.Sprintf("%s %s", str, VerboseFlag)
+	}
+
+	if o.VerboseV {
+		str = fmt.Sprintf("%s %s", str, VerboseVFlag)
+	}
+
+	if o.VerboseVV {
+		str = fmt.Sprintf("%s %s", str, VerboseVVFlag)
+	}
+
+	if o.VerboseVVV {
+		str = fmt.Sprintf("%s %s", str, VerboseVVVFlag)
+	}
+
+	if o.VerboseVVVV {
+		str = fmt.Sprintf("%s %s", str, VerboseVVVVFlag)
+	}
+
+	if o.Version {
+		str = fmt.Sprintf("%s %s", str, VersionFlag)
+	}
+
+	return str
+}
diff --git a/pkg/galaxy/role/role.go b/pkg/galaxy/role/role.go
new file mode 100644
index 0000000..fc0a7e1
--- /dev/null
+++ b/pkg/galaxy/role/role.go
@@ -0,0 +1,4 @@
+package galaxyrole
+
+// AnsibleGalaxyRoleSubCommand is the ansible-galaxy role subcommand
+const AnsibleGalaxyRoleSubCommand = "role"
diff --git a/pkg/inventory/ansibleInventoryCmd.go b/pkg/inventory/ansibleInventoryCmd.go
index b035cde..7850e3f 100644
--- a/pkg/inventory/ansibleInventoryCmd.go
+++ b/pkg/inventory/ansibleInventoryCmd.go
@@ -84,6 +84,38 @@ type AnsibleInventoryCmd struct {
 	InventoryOptions *AnsibleInventoryOptions
 }
 
+// NewAnsibleInventoryCmd creates a new AnsibleInventoryCmd instance
+func NewAnsibleInventoryCmd(options ...AnsibleInventoryOptionFunc) *AnsibleInventoryCmd {
+	cmd := &AnsibleInventoryCmd{}
+
+	for _, option := range options {
+		option(cmd)
+	}
+
+	return cmd
+}
+
+// WithBinary set the ansible-inventory binary file
+func WithBinary(binary string) AnsibleInventoryOptionFunc {
+	return func(p *AnsibleInventoryCmd) {
+		p.Binary = binary
+	}
+}
+
+// WithPattern set the adhoc pattern
+func WithPattern(pattern string) AnsibleInventoryOptionFunc {
+	return func(p *AnsibleInventoryCmd) {
+		p.Pattern = pattern
+	}
+}
+
+// WithInventoryOptions set the ansible-inventory options
+func WithInventoryOptions(options *AnsibleInventoryOptions) AnsibleInventoryOptionFunc {
+	return func(p *AnsibleInventoryCmd) {
+		p.InventoryOptions = options
+	}
+}
+
 // Command generate the ansible command which will be executed
 func (p *AnsibleInventoryCmd) Command() ([]string, error) {
 	cmd := []string{}
diff --git a/pkg/playbook/ansiblePlaybookCmd.go b/pkg/playbook/ansiblePlaybookCmd.go
index 956fa30..2444d24 100644
--- a/pkg/playbook/ansiblePlaybookCmd.go
+++ b/pkg/playbook/ansiblePlaybookCmd.go
@@ -55,10 +55,46 @@ type AnsiblePlaybookCmd struct {
 	PlaybookOptions *AnsiblePlaybookOptions
 }
 
+// NewAnsiblePlaybookCmd creates a new AnsiblePlaybookCmd instance
+func NewAnsiblePlaybookCmd(options ...AnsiblePlaybookOptionsFunc) *AnsiblePlaybookCmd {
+	cmd := &AnsiblePlaybookCmd{}
+
+	for _, option := range options {
+		option(cmd)
+	}
+
+	return cmd
+}
+
+// WithBinary set the ansible-playbook binary file
+func WithBinary(binary string) AnsiblePlaybookOptionsFunc {
+	return func(p *AnsiblePlaybookCmd) {
+		p.Binary = binary
+	}
+}
+
+// WithPlaybookOptions set the ansible-playbook options
+func WithPlaybookOptions(options *AnsiblePlaybookOptions) AnsiblePlaybookOptionsFunc {
+	return func(p *AnsiblePlaybookCmd) {
+		p.PlaybookOptions = options
+	}
+}
+
+// WithPlaybooks set the ansible-playbook playbooks
+func WithPlaybooks(playbooks ...string) AnsiblePlaybookOptionsFunc {
+	return func(p *AnsiblePlaybookCmd) {
+		p.Playbooks = append([]string{}, playbooks...)
+	}
+}
+
 // Command generate the ansible-playbook command which will be executed
 func (p *AnsiblePlaybookCmd) Command() ([]string, error) {
 	cmd := []string{}
 
+	if len(p.Playbooks) == 0 {
+		return nil, errors.New("(playbook::Command)", "No playbooks defined")
+	}
+
 	// Use default binary when it is not already defined
 	if p.Binary == "" {
 		p.Binary = DefaultAnsiblePlaybookBinary
diff --git a/pkg/playbook/ansiblePlaybookOptions.go b/pkg/playbook/ansiblePlaybookOptions.go
index 45ab507..abb2acb 100644
--- a/pkg/playbook/ansiblePlaybookOptions.go
+++ b/pkg/playbook/ansiblePlaybookOptions.go
@@ -412,22 +412,22 @@ func (o *AnsiblePlaybookOptions) GenerateCommandOptions() ([]string, error) {
 
 	if o.SCPExtraArgs != "" {
 		cmd = append(cmd, SCPExtraArgsFlag)
-		cmd = append(cmd, fmt.Sprintf("'%s'", o.SCPExtraArgs))
+		cmd = append(cmd, o.SCPExtraArgs)
 	}
 
 	if o.SFTPExtraArgs != "" {
 		cmd = append(cmd, SFTPExtraArgsFlag)
-		cmd = append(cmd, fmt.Sprintf("'%s'", o.SFTPExtraArgs))
+		cmd = append(cmd, o.SFTPExtraArgs)
 	}
 
 	if o.SSHCommonArgs != "" {
 		cmd = append(cmd, SSHCommonArgsFlag)
-		cmd = append(cmd, fmt.Sprintf("'%s'", o.SSHCommonArgs))
+		cmd = append(cmd, o.SSHCommonArgs)
 	}
 
 	if o.SSHExtraArgs != "" {
 		cmd = append(cmd, SSHExtraArgsFlag)
-		cmd = append(cmd, fmt.Sprintf("'%s'", o.SSHExtraArgs))
+		cmd = append(cmd, o.SSHExtraArgs)
 	}
 
 	if o.Timeout > 0 {



Test patch:
diff --git a/pkg/adhoc/ansibleAdhocCmd_test.go b/pkg/adhoc/ansibleAdhocCmd_test.go
index 2390577..f3a4aa9 100644
--- a/pkg/adhoc/ansibleAdhocCmd_test.go
+++ b/pkg/adhoc/ansibleAdhocCmd_test.go
@@ -6,6 +6,56 @@ import (
 	"github.com/stretchr/testify/assert"
 )
 
+// TestNewAnsibleAdhocCmd tests
+func TestNewAnsibleAdhocCmd(t *testing.T) {
+	t.Log("Testing create new AnsibleAdhocCmd")
+
+	tests := []struct {
+		desc                    string
+		expectedAnsibleAdhocCmd *AnsibleAdhocCmd
+		binary                  string
+		pattern                 string
+		adhocOptions            *AnsibleAdhocOptions
+	}{
+		{
+			desc:    "Testing create new AnsibleAdhocCmd",
+			binary:  "custom-binary",
+			pattern: "pattern",
+			adhocOptions: &AnsibleAdhocOptions{
+				Args:       "args",
+				Background: 11,
+				ModuleName: "module-name",
+				OneLine:    true,
+			},
+			expectedAnsibleAdhocCmd: &AnsibleAdhocCmd{
+				Binary:  "custom-binary",
+				Pattern: "pattern",
+				AdhocOptions: &AnsibleAdhocOptions{
+					Args:       "args",
+					Background: 11,
+					ModuleName: "module-name",
+					OneLine:    true,
+				},
+			},
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.desc, func(t *testing.T) {
+			t.Log(test.desc)
+
+			ansibleAdhocCmd := NewAnsibleAdhocCmd(
+				WithBinary(test.binary),
+				WithPattern(test.pattern),
+				WithAdhocOptions(test.adhocOptions),
+			)
+
+			assert.Equal(t, test.expectedAnsibleAdhocCmd, ansibleAdhocCmd, "Unexpected value")
+		})
+	}
+
+}
+
 func TestCommand(t *testing.T) {
 	t.Log("Testing generate ansible adhoc command string")
 
diff --git a/pkg/adhoc/ansibleAdhocOptions_test.go b/pkg/adhoc/ansibleAdhocOptions_test.go
index 23d3b95..9e55ab2 100644
--- a/pkg/adhoc/ansibleAdhocOptions_test.go
+++ b/pkg/adhoc/ansibleAdhocOptions_test.go
@@ -38,10 +38,10 @@ func TestGenerateAnsibleAdhocOptions(t *testing.T) {
 		PlaybookDir:       "playbook-dir",
 		Poll:              12,
 		PrivateKey:        "pk",
-		SCPExtraArgs:      "scp-extra-args",
-		SFTPExtraArgs:     "sftp-extra-args",
-		SSHCommonArgs:     "ssh-common-args",
-		SSHExtraArgs:      "ssh-extra-args",
+		SCPExtraArgs:      "scp-extra-args1 scp-extra-args2",
+		SFTPExtraArgs:     "sftp-extra-args1 sftp-extra-args2",
+		SSHCommonArgs:     "ssh-common-args1 ssh-common-args2",
+		SSHExtraArgs:      "ssh-extra-args1 ssh-extra-args2",
 		SyntaxCheck:       true,
 		Timeout:           10,
 		Tree:              "tree",
@@ -96,13 +96,13 @@ func TestGenerateAnsibleAdhocOptions(t *testing.T) {
 		"--private-key",
 		"pk",
 		"--scp-extra-args",
-		"'scp-extra-args'",
+		"scp-extra-args1 scp-extra-args2",
 		"--sftp-extra-args",
-		"'sftp-extra-args'",
+		"sftp-extra-args1 sftp-extra-args2",
 		"--ssh-common-args",
-		"'ssh-common-args'",
+		"ssh-common-args1 ssh-common-args2",
 		"--ssh-extra-args",
-		"'ssh-extra-args'",
+		"ssh-extra-args1 ssh-extra-args2",
 		"--timeout",
 		"10",
 		"--user",
@@ -116,7 +116,6 @@ func TestGenerateAnsibleAdhocOptions(t *testing.T) {
 	}
 
 	assert.Equal(t, expected, opts)
-
 }
 
 func TestGenerateExtraVarsCommand(t *testing.T) {
diff --git a/pkg/galaxy/collection/install/ansibleGalaxyCollectionInstallCmd_test.go b/pkg/galaxy/collection/install/ansibleGalaxyCollectionInstallCmd_test.go
new file mode 100644
index 0000000..294d452
--- /dev/null
+++ b/pkg/galaxy/collection/install/ansibleGalaxyCollectionInstallCmd_test.go
@@ -0,0 +1,269 @@
+package galaxycollectioninstall
+
+import (
+	"testing"
+
+	"github.com/apenella/go-ansible/v2/pkg/galaxy"
+	galaxycollection "github.com/apenella/go-ansible/v2/pkg/galaxy/collection"
+	errors "github.com/apenella/go-common-utils/error"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestNewAnsibleGalaxyCollectionInstallCmd(t *testing.T) {
+	cmd := NewAnsibleGalaxyCollectionInstallCmd(
+		WithBinary("ansible-galaxy-binary"),
+		WithCollectionNames("collection-name-1", "collection-name-2"),
+		WithGalaxyCollectionInstallOptions(&AnsibleGalaxyCollectionInstallOptions{
+			Force: true,
+		}),
+	)
+
+	expect := &AnsibleGalaxyCollectionInstallCmd{
+		Binary:          "ansible-galaxy-binary",
+		CollectionNames: []string{"collection-name-1", "collection-name-2"},
+		GalaxyCollectionInstallOptions: &AnsibleGalaxyCollectionInstallOptions{
+			Force: true,
+		},
+	}
+
+	assert.Equal(t, expect, cmd)
+}
+
+func TestAnsibleGalaxyCollectionInstallCmdCommand(t *testing.T) {
+
+	tests := []struct {
+		desc    string
+		cmd     *AnsibleGalaxyCollectionInstallCmd
+		command []string
+		err     error
+	}{
+		{
+			desc: "Testing generate a command for AnsibleGalaxyCollectionInstallCmd with all flags using default binary",
+			cmd: NewAnsibleGalaxyCollectionInstallCmd(
+				WithCollectionNames("collection-name"),
+				WithGalaxyCollectionInstallOptions(&AnsibleGalaxyCollectionInstallOptions{
+					ClearResponseCache:          true,
+					DisableGPGVerify:            true,
+					ForceWithDeps:               true,
+					IgnoreSignatureStatusCode:   true,
+					IgnoreSignatureStatusCodes:  "ignore_codes",
+					Keyring:                     "keyring",
+					NoCache:                     true,
+					Offline:                     true,
+					Pre:                         true,
+					RequiredValidSignatureCount: 1,
+					Signature:                   "signature",
+					Timeout:                     "10",
+					Token:                       "token",
+					APIKey:                      "apikey",
+					Upgrade:                     true,
+					IgnoreCerts:                 true,
+					Force:                       true,
+					IgnoreErrors:                true,
+					NoDeps:                      true,
+					CollectionsPath:             "path",
+					RequirementsFile:            "requirements",
+					Server:                      "server",
+					Verbose:                     true,
+					Version:                     true,
+				}),
+			),
+			err: &errors.Error{},
+			command: []string{
+				galaxy.DefaultAnsibleGalaxyBinary,
+				galaxycollection.AnsibleGalaxyCollectionSubCommand,
+				AnsibleGalaxyCollectionInstallSubCommand,
+				APIKeyFlag, "apikey",
+				ClearResponseCacheFlag,
+				DisableGPGVerifyFlag,
+				ForceWithDepsFlag,
+				IgnoreSignatureStatusCodeFlag,
+				IgnoreSignatureStatusCodesFlag, "ignore_codes",
+				KeyringFlag, "keyring",
+				NoCacheFlag,
+				OfflineFlag,
+				PreFlag,
+				RequiredValidSignatureCountFlag, "1",
+				SignatureFlag, "signature",
+				TimeoutFlag, "10",
+				TokenFlag, "token",
+				UpgradeFlag,
+				IgnoreCertsFlag,
+				ForceFlag,
+				IgnoreErrorsFlag,
+				NoDepsFlag,
+				CollectionsPathFlag, "path",
+				RequirementsFileFlag, "requirements",
+				ServerFlag, "server",
+				VerboseFlag,
+				VersionFlag,
+				"collection-name",
+			},
+		},
+		{
+			desc: "Testing generate a command for AnsibleGalaxyCollectionInstallCmd with all flags",
+			cmd: NewAnsibleGalaxyCollectionInstallCmd(
+				WithBinary("ansible-galaxy-binary"),
+				WithCollectionNames("collection-name"),
+				WithGalaxyCollectionInstallOptions(&AnsibleGalaxyCollectionInstallOptions{
+					ClearResponseCache:          true,
+					DisableGPGVerify:            true,
+					ForceWithDeps:               true,
+					IgnoreSignatureStatusCode:   true,
+					IgnoreSignatureStatusCodes:  "ignore_codes",
+					Keyring:                     "keyring",
+					NoCache:                     true,
+					Offline:                     true,
+					Pre:                         true,
+					RequiredValidSignatureCount: 1,
+					Signature:                   "signature",
+					Timeout:                     "10",
+					Token:                       "token",
+					APIKey:                      "apikey",
+					Upgrade:                     true,
+					IgnoreCerts:                 true,
+					Force:                       true,
+					IgnoreErrors:                true,
+					NoDeps:                      true,
+					CollectionsPath:             "path",
+					RequirementsFile:            "requirements",
+					Server:                      "server",
+					Verbose:                     true,
+					Version:                     true,
+				}),
+			),
+			err: &errors.Error{},
+			command: []string{
+				"ansible-galaxy-binary",
+				galaxycollection.AnsibleGalaxyCollectionSubCommand,
+				AnsibleGalaxyCollectionInstallSubCommand,
+				APIKeyFlag, "apikey",
+				ClearResponseCacheFlag,
+				DisableGPGVerifyFlag,
+				ForceWithDepsFlag,
+				IgnoreSignatureStatusCodeFlag,
+				IgnoreSignatureStatusCodesFlag, "ignore_codes",
+				KeyringFlag, "keyring",
+				NoCacheFlag,
+				OfflineFlag,
+				PreFlag,
+				RequiredValidSignatureCountFlag, "1",
+				SignatureFlag, "signature",
+				TimeoutFlag, "10",
+				TokenFlag, "token",
+				UpgradeFlag,
+				IgnoreCertsFlag,
+				ForceFlag,
+				IgnoreErrorsFlag,
+				NoDepsFlag,
+				CollectionsPathFlag, "path",
+				RequirementsFileFlag, "requirements",
+				ServerFlag, "server",
+				VerboseFlag,
+				VersionFlag,
+				"collection-name",
+			},
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.desc, func(t *testing.T) {
+			t.Log(test.desc)
+
+			command, err := test.cmd.Command()
+
+			if err != nil && assert.Error(t, err) {
+				assert.Equal(t, test.err, err)
+			} else {
+				assert.Equal(t, test.command, command, "Unexpected command value")
+			}
+		})
+	}
+}
+
+func TestAnsibleGalaxyCollectionInstallCmdString(t *testing.T) {
+	tests := []struct {
+		desc    string
+		cmd     *AnsibleGalaxyCollectionInstallCmd
+		command string
+	}{
+
+		{
+			desc: "Testing generate a command for AnsibleGalaxyCollectionInstallCmd with all flags using default binary",
+			cmd: NewAnsibleGalaxyCollectionInstallCmd(
+				WithCollectionNames("collection-name"),
+				WithGalaxyCollectionInstallOptions(&AnsibleGalaxyCollectionInstallOptions{
+					ClearResponseCache:          true,
+					DisableGPGVerify:            true,
+					ForceWithDeps:               true,
+					IgnoreSignatureStatusCode:   true,
+					IgnoreSignatureStatusCodes:  "ignore_codes",
+					Keyring:                     "keyring",
+					NoCache:                     true,
+					Offline:                     true,
+					Pre:                         true,
+					RequiredValidSignatureCount: 1,
+					Signature:                   "signature",
+					Timeout:                     "10",
+					Token:                       "token",
+					APIKey:                      "apikey",
+					Upgrade:                     true,
+					IgnoreCerts:                 true,
+					Force:                       true,
+					IgnoreErrors:                true,
+					NoDeps:                      true,
+					CollectionsPath:             "path",
+					RequirementsFile:            "requirements",
+					Server:                      "server",
+					Verbose:                     true,
+					Version:                     true,
+				}),
+			),
+			command: "ansible-galaxy collection install --api-key apikey --clear-response-cache --disable-gpg-verify --force-with-deps --ignore-signature-status-code --ignore-signature-status-codes ignore_codes --keyring keyring --no-cache --offline --pre --required-valid-signature-count 1 --signature signature --timeout 10 --token token --upgrade --ignore-certs --force --ignore-errors --no-deps --collections-path path --requirements-file requirements --server server --verbose --version collection-name",
+		},
+		{
+			desc: "Testing generate a command for AnsibleGalaxyCollectionInstallCmd with all flags",
+			cmd: NewAnsibleGalaxyCollectionInstallCmd(
+				WithBinary("ansible-galaxy-binary"),
+				WithCollectionNames("collection-name"),
+				WithGalaxyCollectionInstallOptions(&AnsibleGalaxyCollectionInstallOptions{
+					ClearResponseCache:          true,
+					DisableGPGVerify:            true,
+					ForceWithDeps:               true,
+					IgnoreSignatureStatusCode:   true,
+					IgnoreSignatureStatusCodes:  "ignore_codes",
+					Keyring:                     "keyring",
+					NoCache:                     true,
+					Offline:                     true,
+					Pre:                         true,
+					RequiredValidSignatureCount: 1,
+					Signature:                   "signature",
+					Timeout:                     "10",
+					Token:                       "token",
+					APIKey:                      "apikey",
+					Upgrade:                     true,
+					IgnoreCerts:                 true,
+					Force:                       true,
+					IgnoreErrors:                true,
+					NoDeps:                      true,
+					CollectionsPath:             "path",
+					RequirementsFile:            "requirements",
+					Server:                      "server",
+					Verbose:                     true,
+					Version:                     true,
+				}),
+			),
+			command: "ansible-galaxy-binary collection install --api-key apikey --clear-response-cache --disable-gpg-verify --force-with-deps --ignore-signature-status-code --ignore-signature-status-codes ignore_codes --keyring keyring --no-cache --offline --pre --required-valid-signature-count 1 --signature signature --timeout 10 --token token --upgrade --ignore-certs --force --ignore-errors --no-deps --collections-path path --requirements-file requirements --server server --verbose --version collection-name",
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.desc, func(t *testing.T) {
+			t.Log(test.desc)
+
+			command := test.cmd.String()
+
+			assert.Equal(t, test.command, command, "Unexpected command value")
+		})
+	}
+}
diff --git a/pkg/galaxy/collection/install/ansibleGalaxyCollectionInstallOptions_test.go b/pkg/galaxy/collection/install/ansibleGalaxyCollectionInstallOptions_test.go
new file mode 100644
index 0000000..b959749
--- /dev/null
+++ b/pkg/galaxy/collection/install/ansibleGalaxyCollectionInstallOptions_test.go
@@ -0,0 +1,160 @@
+package galaxycollectioninstall
+
+import (
+	"testing"
+
+	errors "github.com/apenella/go-common-utils/error"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestAnsibleGalaxyCollectionInstallOptionsGenerateCommandOptions(t *testing.T) {
+
+	errContext := "(galaxy::AnsibleGalaxyCollectionInstallOptions::GenerateCommandOptions)"
+
+	tests := []struct {
+		desc    string
+		options *AnsibleGalaxyCollectionInstallOptions
+		err     error
+		expect  []string
+	}{
+		{
+			desc:    "Testing nil AnsibleGalaxyCollectionInstallOptions definition",
+			options: nil,
+			err:     errors.New(errContext, "AnsibleGalaxyCollectionInstallOptions is nil"),
+			expect:  []string{},
+		},
+		{
+			desc:    "Testing an empty AnsibleGalaxyCollectionInstallOptions definition",
+			options: &AnsibleGalaxyCollectionInstallOptions{},
+			err:     nil,
+			expect:  []string{},
+		},
+		{
+			desc: "Testing AnsibleGalaxyCollectionInstallOptions with all flags",
+			options: &AnsibleGalaxyCollectionInstallOptions{
+				ClearResponseCache:          true,
+				DisableGPGVerify:            true,
+				ForceWithDeps:               true,
+				IgnoreSignatureStatusCode:   true,
+				IgnoreSignatureStatusCodes:  "ignore_codes",
+				Keyring:                     "keyring",
+				NoCache:                     true,
+				Offline:                     true,
+				Pre:                         true,
+				RequiredValidSignatureCount: 1,
+				Signature:                   "signature",
+				Timeout:                     "10",
+				Token:                       "token",
+				APIKey:                      "apikey",
+				Upgrade:                     true,
+				IgnoreCerts:                 true,
+				Force:                       true,
+				IgnoreErrors:                true,
+				NoDeps:                      true,
+				CollectionsPath:             "path",
+				RequirementsFile:            "requirements",
+				Server:                      "server",
+				Verbose:                     true,
+				Version:                     true,
+			},
+			err: nil,
+			expect: []string{
+				APIKeyFlag, "apikey",
+				ClearResponseCacheFlag,
+				DisableGPGVerifyFlag,
+				ForceWithDepsFlag,
+				IgnoreSignatureStatusCodeFlag,
+				IgnoreSignatureStatusCodesFlag, "ignore_codes",
+				KeyringFlag, "keyring",
+				NoCacheFlag,
+				OfflineFlag,
+				PreFlag,
+				RequiredValidSignatureCountFlag, "1",
+				SignatureFlag, "signature",
+				TimeoutFlag, "10",
+				TokenFlag, "token",
+				UpgradeFlag,
+				IgnoreCertsFlag,
+				ForceFlag,
+				IgnoreErrorsFlag,
+				NoDepsFlag,
+				CollectionsPathFlag, "path",
+				RequirementsFileFlag, "requirements",
+				ServerFlag, "server",
+				VerboseFlag,
+				VersionFlag,
+			},
+		},
+	}
+
+	for _, test := range tests {
+
+		t.Run(test.desc, func(t *testing.T) {
+			t.Log(test.desc)
+
+			options, err := test.options.GenerateCommandOptions()
+
+			if err != nil && assert.Error(t, err) {
+				assert.Equal(t, test.err, err)
+			} else {
+				assert.Equal(t, test.expect, options, "Unexpected options value")
+			}
+		})
+	}
+}
+
+func TestAnsibleGalaxyCollectionInstallOptionsString(t *testing.T) {
+	tests := []struct {
+		desc    string
+		options *AnsibleGalaxyCollectionInstallOptions
+		expect  string
+	}{
+		{
+			desc:    "Testing generate string from an empty AnsibleGalaxyCollectionInstallOptions",
+			options: &AnsibleGalaxyCollectionInstallOptions{},
+			expect:  "",
+		},
+		{
+			desc: "Testing generate string from an AnsibleGalaxyCollectionInstallOptions with all flags",
+			options: &AnsibleGalaxyCollectionInstallOptions{
+				ClearResponseCache:          true,
+				DisableGPGVerify:            true,
+				ForceWithDeps:               true,
+				IgnoreSignatureStatusCode:   true,
+				IgnoreSignatureStatusCodes:  "ignore_codes",
+				Keyring:                     "keyring",
+				NoCache:                     true,
+				Offline:                     true,
+				Pre:                         true,
+				RequiredValidSignatureCount: 1,
+				Signature:                   "signature",
+				Timeout:                     "10",
+				Token:                       "token",
+				APIKey:                      "apikey",
+				Upgrade:                     true,
+				IgnoreCerts:                 true,
+				Force:                       true,
+				IgnoreErrors:                true,
+				NoDeps:                      true,
+				CollectionsPath:             "path",
+				RequirementsFile:            "requirements",
+				Server:                      "server",
+				Verbose:                     true,
+				Version:                     true,
+			},
+			expect: "--api-key apikey --clear-response-cache --disable-gpg-verify --force-with-deps --ignore-signature-status-code --ignore-signature-status-codes ignore_codes --keyring keyring --no-cache --offline --pre --required-valid-signature-count 1 --signature signature --timeout 10 --token token --upgrade --ignore-certs --force --ignore-errors --no-deps --collections-path path --requirements-file requirements --server server --verbose --version",
+		},
+	}
+
+	for _, test := range tests {
+
+		t.Run(test.desc, func(t *testing.T) {
+			t.Log(test.desc)
+
+			res := test.options.String()
+
+			assert.Equal(t, test.expect, res, "Unexpected options value for AnsibleGalaxyCollectionInstallOptions.String()")
+
+		})
+	}
+}
diff --git a/pkg/galaxy/role/install/ansibleGalaxyRoleInstallCmd_test.go b/pkg/galaxy/role/install/ansibleGalaxyRoleInstallCmd_test.go
new file mode 100644
index 0000000..2c2a955
--- /dev/null
+++ b/pkg/galaxy/role/install/ansibleGalaxyRoleInstallCmd_test.go
@@ -0,0 +1,225 @@
+package galaxyroleinstall
+
+import (
+	"testing"
+
+	"github.com/apenella/go-ansible/v2/pkg/galaxy"
+	galaxyrole "github.com/apenella/go-ansible/v2/pkg/galaxy/role"
+	errors "github.com/apenella/go-common-utils/error"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestNewAnsibleGalaxyRoleInstallCmd(t *testing.T) {
+	cmd := NewAnsibleGalaxyRoleInstallCmd(
+		WithBinary("ansible-galaxy-binary"),
+		WithRoleNames("nginx"),
+		WithGalaxyRoleInstallOptions(&AnsibleGalaxyRoleInstallOptions{
+			Force: true,
+		}),
+	)
+
+	expect := &AnsibleGalaxyRoleInstallCmd{
+		Binary:    "ansible-galaxy-binary",
+		RoleNames: []string{"nginx"},
+		GalaxyRoleInstallOptions: &AnsibleGalaxyRoleInstallOptions{
+			Force: true,
+		},
+	}
+
+	assert.Equal(t, expect, cmd)
+}
+
+func TestAnsibleGalaxyRoleInstallCmdCommand(t *testing.T) {
+
+	tests := []struct {
+		desc    string
+		cmd     *AnsibleGalaxyRoleInstallCmd
+		command []string
+		err     error
+	}{
+		{
+			desc: "Testing generate a command for AnsibleGalaxyRoleInstallCmd with all flags using default binary",
+			cmd: NewAnsibleGalaxyRoleInstallCmd(
+				WithRoleNames("role-name"),
+				WithGalaxyRoleInstallOptions(&AnsibleGalaxyRoleInstallOptions{
+					ApiKey:        "apikey",
+					Force:         true,
+					ForceWithDeps: true,
+					IgnoreCerts:   true,
+					IgnoreErrors:  true,
+					KeepSCMMeta:   true,
+					NoDeps:        true,
+					RoleFile:      "rolefile",
+					RolesPath:     "rolespath",
+					Server:        "server",
+					Timeout:       "timeout",
+					Token:         "token",
+					Verbose:       true,
+					VerboseV:      true,
+					VerboseVV:     true,
+					VerboseVVV:    true,
+					VerboseVVVV:   true,
+					Version:       true,
+				}),
+			),
+			err: &errors.Error{},
+			command: []string{
+				galaxy.DefaultAnsibleGalaxyBinary,
+				galaxyrole.AnsibleGalaxyRoleSubCommand,
+				AnsibleGalaxyRoleInstallSubCommand,
+				APIKeyFlag, "apikey",
+				ForceFlag,
+				ForceWithDepsFlag,
+				IgnoreCertsFlag,
+				IgnoreErrorsFlag,
+				KeepSCMMetaFlag,
+				NoDepsFlag,
+				RoleFileFlag, "rolefile",
+				RolesPathFlag, "rolespath",
+				ServerFlag, "server",
+				TimeoutFlag, "timeout",
+				TokenFlag, "token",
+				VerboseVVVVFlag,
+				VersionFlag,
+				"role-name",
+			},
+		},
+		{
+			desc: "Testing generate a command for AnsibleGalaxyRoleInstallCmd with all flags",
+			cmd: NewAnsibleGalaxyRoleInstallCmd(
+				WithBinary("ansible-galaxy-binary"),
+				WithRoleNames("role-name"),
+				WithGalaxyRoleInstallOptions(&AnsibleGalaxyRoleInstallOptions{
+					ApiKey:        "apikey",
+					Force:         true,
+					ForceWithDeps: true,
+					IgnoreCerts:   true,
+					IgnoreErrors:  true,
+					KeepSCMMeta:   true,
+					NoDeps:        true,
+					RoleFile:      "rolefile",
+					RolesPath:     "rolespath",
+					Server:        "server",
+					Timeout:       "timeout",
+					Token:         "token",
+					Verbose:       true,
+					VerboseV:      true,
+					VerboseVV:     true,
+					VerboseVVV:    true,
+					VerboseVVVV:   true,
+					Version:       true,
+				}),
+			),
+			err: &errors.Error{},
+			command: []string{
+				"ansible-galaxy-binary",
+				galaxyrole.AnsibleGalaxyRoleSubCommand,
+				AnsibleGalaxyRoleInstallSubCommand,
+				APIKeyFlag, "apikey",
+				ForceFlag,
+				ForceWithDepsFlag,
+				IgnoreCertsFlag,
+				IgnoreErrorsFlag,
+				KeepSCMMetaFlag,
+				NoDepsFlag,
+				RoleFileFlag, "rolefile",
+				RolesPathFlag, "rolespath",
+				ServerFlag, "server",
+				TimeoutFlag, "timeout",
+				TokenFlag, "token",
+				VerboseVVVVFlag,
+				VersionFlag,
+				"role-name",
+			},
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.desc, func(t *testing.T) {
+			t.Log(test.desc)
+
+			command, err := test.cmd.Command()
+
+			if err != nil && assert.Error(t, err) {
+				assert.Equal(t, test.err, err)
+			} else {
+				assert.Equal(t, test.command, command, "Unexpected command value")
+			}
+		})
+	}
+}
+
+func TestAnsibleGalaxyRoleInstallCmdString(t *testing.T) {
+	tests := []struct {
+		desc    string
+		cmd     *AnsibleGalaxyRoleInstallCmd
+		command string
+	}{
+
+		{
+			desc: "Testing generate a command for AnsibleGalaxyRoleInstallCmd with all flags using default binary",
+			cmd: NewAnsibleGalaxyRoleInstallCmd(
+				WithRoleNames("role-name"),
+				WithGalaxyRoleInstallOptions(&AnsibleGalaxyRoleInstallOptions{
+					ApiKey:        "apikey",
+					Force:         true,
+					ForceWithDeps: true,
+					IgnoreCerts:   true,
+					IgnoreErrors:  true,
+					KeepSCMMeta:   true,
+					NoDeps:        true,
+					RoleFile:      "rolefile",
+					RolesPath:     "rolespath",
+					Server:        "server",
+					Timeout:       "timeout",
+					Token:         "token",
+					Verbose:       true,
+					VerboseV:      true,
+					VerboseVV:     true,
+					VerboseVVV:    true,
+					VerboseVVVV:   true,
+					Version:       true,
+				}),
+			),
+			command: "ansible-galaxy role install  --api-key apikey --force --force-with-deps --ignore-certs --ignore-errors --keep-scm-meta --no-deps --role-file rolefile --roles-path rolespath --server server --timeout timeout --token token -vvvv -v -vv -vvv -vvvv --version role-name",
+		},
+		{
+			desc: "Testing generate a command for AnsibleGalaxyRoleInstallCmd with all flags",
+			cmd: NewAnsibleGalaxyRoleInstallCmd(
+				WithBinary("ansible-galaxy-binary"),
+				WithRoleNames("role-name"),
+				WithGalaxyRoleInstallOptions(&AnsibleGalaxyRoleInstallOptions{
+					ApiKey:        "apikey",
+					Force:         true,
+					ForceWithDeps: true,
+					IgnoreCerts:   true,
+					IgnoreErrors:  true,
+					KeepSCMMeta:   true,
+					NoDeps:        true,
+					RoleFile:      "rolefile",
+					RolesPath:     "rolespath",
+					Server:        "server",
+					Timeout:       "timeout",
+					Token:         "token",
+					Verbose:       true,
+					VerboseV:      true,
+					VerboseVV:     true,
+					VerboseVVV:    true,
+					VerboseVVVV:   true,
+					Version:       true,
+				}),
+			),
+			command: "ansible-galaxy-binary role install  --api-key apikey --force --force-with-deps --ignore-certs --ignore-errors --keep-scm-meta --no-deps --role-file rolefile --roles-path rolespath --server server --timeout timeout --token token -vvvv -v -vv -vvv -vvvv --version role-name",
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.desc, func(t *testing.T) {
+			t.Log(test.desc)
+
+			command := test.cmd.String()
+
+			assert.Equal(t, test.command, command, "Unexpected command value")
+		})
+	}
+}
diff --git a/pkg/galaxy/role/install/ansibleGalaxyRoleInstallOptions_test.go b/pkg/galaxy/role/install/ansibleGalaxyRoleInstallOptions_test.go
new file mode 100644
index 0000000..3723169
--- /dev/null
+++ b/pkg/galaxy/role/install/ansibleGalaxyRoleInstallOptions_test.go
@@ -0,0 +1,210 @@
+package galaxyroleinstall
+
+import (
+	"testing"
+
+	errors "github.com/apenella/go-common-utils/error"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestAnsibleGalaxyRoleInstallOptionsGenerateCommandOptions(t *testing.T) {
+
+	errContext := "(galaxy::AnsibleGalaxyRoleInstallOptions::GenerateCommandOptions)"
+
+	tests := []struct {
+		desc    string
+		options *AnsibleGalaxyRoleInstallOptions
+		err     error
+		expect  []string
+	}{
+		{
+			desc:    "Testing nil AnsibleGalaxyRoleInstallOptions definition",
+			options: nil,
+			err:     errors.New(errContext, "AnsibleGalaxyRoleInstallOptions is nil"),
+			expect:  []string{},
+		},
+		{
+			desc:    "Testing an empty AnsibleGalaxyRoleInstallOptions definition",
+			options: &AnsibleGalaxyRoleInstallOptions{},
+			err:     nil,
+			expect:  []string{},
+		},
+		{
+			desc: "Testing AnsibleGalaxyRoleInstallOptions with all flags",
+			options: &AnsibleGalaxyRoleInstallOptions{
+				ApiKey:        "apikey",
+				Force:         true,
+				ForceWithDeps: true,
+				IgnoreCerts:   true,
+				IgnoreErrors:  true,
+				KeepSCMMeta:   true,
+				NoDeps:        true,
+				RoleFile:      "rolefile",
+				RolesPath:     "rolespath",
+				Server:        "server",
+				Timeout:       "timeout",
+				Token:         "token",
+				Verbose:       true,
+				VerboseV:      true,
+				VerboseVV:     true,
+				VerboseVVV:    true,
+				VerboseVVVV:   true,
+				Version:       true,
+			},
+			err: nil,
+			expect: []string{
+				APIKeyFlag, "apikey",
+				ForceFlag,
+				ForceWithDepsFlag,
+				IgnoreCertsFlag,
+				IgnoreErrorsFlag,
+				KeepSCMMetaFlag,
+				NoDepsFlag,
+				RoleFileFlag, "rolefile",
+				RolesPathFlag, "rolespath",
+				ServerFlag, "server",
+				TimeoutFlag, "timeout",
+				TokenFlag, "token",
+				VerboseVVVVFlag,
+				VersionFlag,
+			},
+		},
+	}
+
+	for _, test := range tests {
+
+		t.Run(test.desc, func(t *testing.T) {
+			t.Log(test.desc)
+
+			options, err := test.options.GenerateCommandOptions()
+
+			if err != nil && assert.Error(t, err) {
+				assert.Equal(t, test.err, err)
+			} else {
+				assert.Equal(t, test.expect, options, "Unexpected options value")
+			}
+		})
+	}
+}
+
+func TestAnsibleGalaxyRoleInstallOptionsGenerateVerbosityFlag(t *testing.T) {
+	tests := []struct {
+		desc    string
+		options *AnsibleGalaxyRoleInstallOptions
+		res     string
+		err     error
+	}{
+		{
+			desc: "Testing generate verbosity flag",
+			options: &AnsibleGalaxyRoleInstallOptions{
+				Verbose: true,
+			},
+			res: "-vvvv",
+			err: &errors.Error{},
+		},
+		{
+			desc: "Testing generate verbosity flag V",
+			options: &AnsibleGalaxyRoleInstallOptions{
+				VerboseV: true,
+			},
+			res: "-v",
+			err: &errors.Error{},
+		},
+		{
+			desc: "Testing generate verbosity flag VV",
+			options: &AnsibleGalaxyRoleInstallOptions{
+				VerboseVV: true,
+			},
+			res: "-vv",
+			err: &errors.Error{},
+		},
+		{
+			desc: "Testing generate verbosity flag VVV",
+			options: &AnsibleGalaxyRoleInstallOptions{
+				VerboseVVV: true,
+			},
+			res: "-vvv",
+			err: &errors.Error{},
+		},
+		{
+			desc: "Testing generate verbosity flag VVVV",
+			options: &AnsibleGalaxyRoleInstallOptions{
+				VerboseVVVV: true,
+			},
+			res: "-vvvv",
+			err: &errors.Error{},
+		},
+		{
+			desc: "Testing generate verbosity flag VV has precedence over V",
+			options: &AnsibleGalaxyRoleInstallOptions{
+				VerboseVV: true,
+				VerboseV:  true,
+			},
+			res: "-vv",
+			err: &errors.Error{},
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.desc, func(t *testing.T) {
+			t.Log(test.desc)
+
+			res, err := test.options.generateVerbosityFlag()
+			if err != nil && assert.Error(t, err) {
+				assert.Equal(t, test.err, err)
+			} else {
+				assert.Equal(t, test.res, res)
+			}
+		})
+	}
+}
+
+func TestAnsibleGalaxyRoleInstallOptionsString(t *testing.T) {
+	tests := []struct {
+		desc    string
+		options *AnsibleGalaxyRoleInstallOptions
+		expect  string
+	}{
+		{
+			desc:    "Testing generate string from an empty AnsibleGalaxyRoleInstallOptions",
+			options: &AnsibleGalaxyRoleInstallOptions{},
+			expect:  "",
+		},
+		{
+			desc: "Testing generate string from an AnsibleGalaxyRoleInstallOptions with all flags",
+			options: &AnsibleGalaxyRoleInstallOptions{
+				ApiKey:        "apikey",
+				Force:         true,
+				ForceWithDeps: true,
+				IgnoreCerts:   true,
+				IgnoreErrors:  true,
+				KeepSCMMeta:   true,
+				NoDeps:        true,
+				RoleFile:      "rolefile",
+				RolesPath:     "rolespath",
+				Server:        "server",
+				Timeout:       "timeout",
+				Token:         "token",
+				Verbose:       true,
+				VerboseV:      true,
+				VerboseVV:     true,
+				VerboseVVV:    true,
+				VerboseVVVV:   true,
+				Version:       true,
+			},
+			expect: " --api-key apikey --force --force-with-deps --ignore-certs --ignore-errors --keep-scm-meta --no-deps --role-file rolefile --roles-path rolespath --server server --timeout timeout --token token -vvvv -v -vv -vvv -vvvv --version",
+		},
+	}
+
+	for _, test := range tests {
+
+		t.Run(test.desc, func(t *testing.T) {
+			t.Log(test.desc)
+
+			res := test.options.String()
+
+			assert.Equal(t, test.expect, res, "Unexpected options value for AnsibleGalaxyRoleInstallOptions.String()")
+
+		})
+	}
+}
diff --git a/pkg/inventory/ansibleInventoryCmd_test.go b/pkg/inventory/ansibleInventoryCmd_test.go
index 93f356f..0022cd1 100644
--- a/pkg/inventory/ansibleInventoryCmd_test.go
+++ b/pkg/inventory/ansibleInventoryCmd_test.go
@@ -7,6 +7,78 @@ import (
 	"github.com/stretchr/testify/assert"
 )
 
+// TestNewAnsibleInventoryCmd tests
+func TestNewAnsibleInventoryCmd(t *testing.T) {
+
+	tests := []struct {
+		desc                        string
+		expectedAnsibleInventoryCmd *AnsibleInventoryCmd
+		binary                      string
+		pattern                     string
+		ansibleInventoryOptions     *AnsibleInventoryOptions
+	}{
+		{
+			desc:    "Testing create new AnsibleInventoryCmd",
+			binary:  "custom-binary",
+			pattern: "pattern",
+			ansibleInventoryOptions: &AnsibleInventoryOptions{
+				AskVaultPassword:  true,
+				Export:            true,
+				Graph:             true,
+				Host:              "localhost",
+				Inventory:         "test/ansible/inventory/all",
+				Limit:             "limit",
+				List:              true,
+				Output:            "/tmp/output.ini",
+				PlaybookDir:       "playbook-dir",
+				Toml:              true,
+				Vars:              true,
+				VaultID:           "vault-id",
+				VaultPasswordFile: "vault-password-file",
+				Verbose:           true,
+				Version:           true,
+				Yaml:              true,
+			},
+			expectedAnsibleInventoryCmd: &AnsibleInventoryCmd{
+				Binary:  "custom-binary",
+				Pattern: "pattern",
+				InventoryOptions: &AnsibleInventoryOptions{
+					AskVaultPassword:  true,
+					Export:            true,
+					Graph:             true,
+					Host:              "localhost",
+					Inventory:         "test/ansible/inventory/all",
+					Limit:             "limit",
+					List:              true,
+					Output:            "/tmp/output.ini",
+					PlaybookDir:       "playbook-dir",
+					Toml:              true,
+					Vars:              true,
+					VaultID:           "vault-id",
+					VaultPasswordFile: "vault-password-file",
+					Verbose:           true,
+					Version:           true,
+					Yaml:              true,
+				},
+			},
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.desc, func(t *testing.T) {
+			t.Log(test.desc)
+
+			ansibleInventoryCmd := NewAnsibleInventoryCmd(
+				WithBinary(test.binary),
+				WithPattern(test.pattern),
+				WithInventoryOptions(test.ansibleInventoryOptions),
+			)
+
+			assert.Equal(t, test.expectedAnsibleInventoryCmd, ansibleInventoryCmd, "Unexpected value")
+		})
+	}
+}
+
 // TestGenerateCommandOptions tests
 func TestGenerateCommandOptions(t *testing.T) {
 	tests := []struct {
diff --git a/pkg/playbook/ansiblePlaybookCmd_test.go b/pkg/playbook/ansiblePlaybookCmd_test.go
index b6ddd2c..0a9b595 100644
--- a/pkg/playbook/ansiblePlaybookCmd_test.go
+++ b/pkg/playbook/ansiblePlaybookCmd_test.go
@@ -6,6 +6,53 @@ import (
 	"github.com/stretchr/testify/assert"
 )
 
+// TestNewAnsiblePlaybookCmd tests
+func TestNewAnsiblePlaybookCmd(t *testing.T) {
+	tests := []struct {
+		desc                       string
+		expectedAnsiblePlaybookCmd *AnsiblePlaybookCmd
+		binary                     string
+		playbooks                  []string
+		playbookOptions            *AnsiblePlaybookOptions
+	}{
+		{
+			desc:      "Testing create new AnsiblePlaybookCmd",
+			binary:    "custom-ansible-playbook",
+			playbooks: []string{"test/ansible/site.yml", "test/ansible/site2.yml"},
+			playbookOptions: &AnsiblePlaybookOptions{
+				AskBecomePass:    true,
+				AskPass:          true,
+				AskVaultPassword: true,
+				Become:           true,
+			},
+			expectedAnsiblePlaybookCmd: &AnsiblePlaybookCmd{
+				Binary:    "custom-ansible-playbook",
+				Playbooks: []string{"test/ansible/site.yml", "test/ansible/site2.yml"},
+				PlaybookOptions: &AnsiblePlaybookOptions{
+					AskBecomePass:    true,
+					AskPass:          true,
+					AskVaultPassword: true,
+					Become:           true,
+				},
+			},
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.desc, func(t *testing.T) {
+			t.Log(test.desc)
+
+			ansiblePlaybookCmd := NewAnsiblePlaybookCmd(
+				WithBinary(test.binary),
+				WithPlaybooks(test.playbooks...),
+				WithPlaybookOptions(test.playbookOptions),
+			)
+
+			assert.Equal(t, test.expectedAnsiblePlaybookCmd, ansiblePlaybookCmd, "Unexpected value")
+		})
+	}
+}
+
 // TestCommand tests
 func TestCommand(t *testing.T) {
 	tests := []struct {
diff --git a/pkg/playbook/ansiblePlaybookOptions_test.go b/pkg/playbook/ansiblePlaybookOptions_test.go
index 0d6e11e..05771ac 100644
--- a/pkg/playbook/ansiblePlaybookOptions_test.go
+++ b/pkg/playbook/ansiblePlaybookOptions_test.go
@@ -54,11 +54,11 @@ func TestGenerateCommandOptions(t *testing.T) {
 				ListTasks:         true,
 				ModulePath:        "module-path",
 				PrivateKey:        "private-key",
-				SCPExtraArgs:      "scp-extra-args",
-				SFTPExtraArgs:     "sftp-extra-args",
+				SCPExtraArgs:      "scp-extra-args1 scp-extra-args2",
+				SFTPExtraArgs:     "sftp-extra-args1 sftp-extra-args2",
 				SkipTags:          "skip-tags",
-				SSHCommonArgs:     "ssh-common-args",
-				SSHExtraArgs:      "ssh-extra-args",
+				SSHCommonArgs:     "ssh-common-args1 ssh-common-args2",
+				SSHExtraArgs:      "ssh-extra-args1 ssh-extra-args2",
 				StartAtTask:       "start-at-task",
 				Step:              true,
 				SyntaxCheck:       true,
@@ -71,7 +71,7 @@ func TestGenerateCommandOptions(t *testing.T) {
 				Version:           true,
 			},
 			err:     nil,
-			options: []string{"--ask-vault-password", "--check", "--diff", "--extra-vars", "{\"extra\":\"var\"}", "--extra-vars", "@test.yml", "--flush-cache", "--force-handlers", "--forks", "10", "--inventory", "inventory", "--limit", "limit", "--list-hosts", "--list-tags", "--list-tasks", "--module-path", "module-path", "--skip-tags", "skip-tags", "--start-at-task", "start-at-task", "--step", "--syntax-check", "--tags", "tags", "--vault-id", "vault-ID", "--vault-password-file", "vault-password-file", "-vvvv", "--version", "--ask-pass", "--connection", "local", "--private-key", "private-key", "--scp-extra-args", "'scp-extra-args'", "--sftp-extra-args", "'sftp-extra-args'", "--ssh-common-args", "'ssh-common-args'", "--ssh-extra-args", "'ssh-extra-args'", "--timeout", "11", "--user", "user", "--ask-become-pass", "--become", "--become-method", "become-method", "--become-user", "become-user"},
+			options: []string{"--ask-vault-password", "--check", "--diff", "--extra-vars", "{\"extra\":\"var\"}", "--extra-vars", "@test.yml", "--flush-cache", "--force-handlers", "--forks", "10", "--inventory", "inventory", "--limit", "limit", "--list-hosts", "--list-tags", "--list-tasks", "--module-path", "module-path", "--skip-tags", "skip-tags", "--start-at-task", "start-at-task", "--step", "--syntax-check", "--tags", "tags", "--vault-id", "vault-ID", "--vault-password-file", "vault-password-file", "-vvvv", "--version", "--ask-pass", "--connection", "local", "--private-key", "private-key", "--scp-extra-args", "scp-extra-args1 scp-extra-args2", "--sftp-extra-args", "sftp-extra-args1 sftp-extra-args2", "--ssh-common-args", "ssh-common-args1 ssh-common-args2", "--ssh-extra-args", "ssh-extra-args1 ssh-extra-args2", "--timeout", "11", "--user", "user", "--ask-become-pass", "--become", "--become-method", "become-method", "--become-user", "become-user"},
 		},
 	}