Skip to content

Go-ansible is a Go package that enables the execution of ansible-playbook or ansible commands directly from Golang applications. It supports a wide range of options for each command, enabling smooth integration of Ansible functionality into your projects.

License

Notifications You must be signed in to change notification settings

apenella/go-ansible

Repository files navigation

go-ansible

License: MIT Test Go Report Card Go Reference

go-ansible-logo

Go-ansible is a Go package that enables the execution of ansible-playbook or ansible commands directly from Golang applications. It supports a wide range of options for each command, enabling smooth integration of Ansible functionality into your projects. Let's dive in and explore the capabilities of go-ansible together.

Disclaimer: Please note that the master branch may contain unreleased features. Be aware of this when utilizing the library in your projects.

Install

To install the latest stable version of go-ansible, run the following command:

go get github.com/apenella/go-ansible@v1.2.0

This command will fetch and install the latest version of go-ansible, ensuring that you have the most up-to-date and stable release.

Upgrade to 1.x

If you are currently using a go-ansible version prior to 1.0.0, it's important to note that there have been significant breaking changes introduced in version 1.0.0 and beyond. Before proceeding with the upgrade, we highly recommend reading the changelog and the upgrade guide carefully. These resources provide detailed information on the changes and steps required for a smooth transition to the new version.

Getting Started

This section will guide you through the step-by-step process of using the go-ansible library. Follow these instructions to create an application that utilizes the ansible-playbook utility.

Before proceeding, make sure you have the latest version of the go-ansible library installed. If you haven't done so yet, please refer to the Installation section for instructions on how to install the library.

At this point, you are ready to define the required structs and execute the ansible-playbook command using the go-ansible library.

First, let's define the AnsiblePlaybookConnectionOptions struct for connection options:

ansiblePlaybookConnectionOptions := &options.AnsiblePlaybookConnectionOptions{
  Connection: "local",
}

Next, define the AnsiblePlaybookOptions struct to specify the execution behaviour and configuration location:

ansiblePlaybookOptions := &playbook.AnsiblePlaybookOptions{
  Inventory: "127.0.0.1,",
}

Then, define the AnsiblePlaybookPrivilegeEscalationOptions struct to specify privilege escalation requirements:

privilegeEscalationOptions := &options.AnsiblePlaybookPrivilegeEscalationOptions{
  Become:        true,
  BecomeMethod:  "sudo",
}

Finally, create the AnsiblePlaybookCmd struct to define the command execution:

cmd := &playbook.AnsiblePlaybookCmd{
  Playbook:          "site.yml",
  ConnectionOptions: ansiblePlaybookConnectionOptions,
  Options:           ansiblePlaybookOptions,
  PrivilegeEscalationOptions: privilegeEscalationOptions,
}

Once the AnsiblePlaybookCmd is defined, you can execute it using the Run method. If an executor is not explicitly defined, DefaultExecute is used with the default parameters:

err := cmd.Run(context.TODO())
if err != nil {
  panic(err)
}

The output of the ansible-playbook execution will be similar to the following:

 ──
 ── PLAY [all] *********************************************************************
 ──
 ── TASK [Gathering Facts] *********************************************************
 ── ok: [127.0.0.1]
 ──
 ── TASK [simple-ansibleplaybook] **************************************************
 ── ok: [127.0.0.1] => {
 ──     "msg": "Your are running 'simple-ansibleplaybook' example"
 ── }
 ──
 ── PLAY RECAP *********************************************************************
 ── 127.0.0.1                  : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
 ──
 ── Playbook run took 0 days, 0 hours, 0 minutes, 0 seconds

Usage Reference

The development reference is a comprehensive resource that provides everything you need to know to effectively use the library go-ansible.

Packages

This section describes the different packages and their resources available in the go-ansible library. You can find the complete reference of structs, methods and functions here.

Adhoc

The information provided in this section highlights the key components and functionalities of the adhoc package within go-ansible.

The github.com/apenella/go-ansible/pkg/adhoc package enables you to execute Ansible ad-hoc commands. To execute these commands, you can utilize the following ad-hoc structs:

  • AnsibleAdhocCmd: This main struct defines the Ansible ad-hoc command and specifies how to execute it. It is mandatory to define an AnsibleAdhocCmd to run the command. The AnsibleAdhocCmd requires a parameter to specify which Executor to use. The executor serves as the worker responsible for launching the execution. If no Executor is explicitly specified, the command uses the DefaultExecute.

  • AnsibleAdhocOptions: This struct provides parameters described in the Options section within Ansible's manual page. It defines the behaviour of the Ansible execution and specifies where to find the execution configuration.

Additionally, you can provide privilege escalation options or connection options to the AnsibleAdhocCmd. These options are defined in the github.com/apenella/go-ansible/pkg/options package. Refer to the options sections to know more about it.

Playbook

The information provided in this section gives an overview of the playbook package in go-ansible.

The github.com/apenella/go-ansible/pkg/playbook package provides the functionality to execute ansible-playbook commands. To run ansible-playbook commands, you can use the following types:

  • AnsiblePlaybookCmd: This is the main object type that defines the ansible-playbook command and specifies how to execute it. It is mandatory to define an AnsiblePlaybookCmd to run any ansible-playbook command. The AnsiblePlaybookCmd includes a parameter that defines the Executor to use, which acts as the worker responsible for launching the execution. If no Executor is explicitly specified, the DefaultExecute is used by default.
  • AnsiblePlaybookOptions: This type includes the parameters described in the Options section within Ansible's manual page. It defines the execution behaviour of the ansible-playbook and specifies where to find the execution configuration.

Additionally, you can provide privilege escalation options or connection options to the AnsiblePlaybookCmd. These options are defined in the github.com/apenella/go-ansible/pkg/options package. Refer to the options sections to know more about it.

Execute

An executor in go-ansible is a component that executes the command and retrieves the results from stdout and stderr. The library includes a default executor implementation called DefaultExecute, which is located in the github.com/apenella/go-ansible/pkg/execute package. The DefaultExecute executor adheres to the Executor interface.

The Executor interface requires the implementation of the Execute method with the following signature:

// Executor interface is satisfied by those types which has a Execute(context.Context,[]string,stdoutcallback.StdoutCallbackResultsFunc,...ExecuteOptions)error method
type Executor interface {
  Execute(ctx context.Context, command []string, resultsFunc stdoutcallback.StdoutCallbackResultsFunc, options ...ExecuteOptions) error
}
DefaultExecute

The DefaultExecute executor, provided by go-ansible, writes the command's stdout and stderr to the system stdout by default. However, it can be easily customized and extended to handle stdout and stderr differently. This can be achieved using the ExecuteOptions functions, which can be passed to the executor.

// ExecuteOptions is a function to set executor options
type ExecuteOptions func(Executor)

The DefaultExecute executor also allows the configuration of Ansible through environment variables. This can be done using the WithEnvVar function, which injects environment variables into the command before its execution. To apply Ansible parameters as environment variables, pass WithEnvVar as an ExecuteOption to NewDefaultExecute.

To further customize the way results are returned to the user, go-ansible provides transformers. These transformers can be added to the DefaultExecute using the WithTransformers(...results.TransformerFunc) function, which can be included as an ExecuteOption.

For more examples and practical use cases, refer to the examples directory in the go-ansible repository.

Custom executor

If the DefaultExecute executor does not meet your requirements or expectations, you have the flexibility to implement a custom executor and set it on the AnsiblePlaybookCmd struct. The AnsiblePlaybookCmd expects a struct that implements the Executor interface.

Here is an example of a custom executor that demonstrates how to implement a custom executor and integrate it with the AnsiblePlaybookCmd or AnsibleAdhocCmd structs to execute the playbook with your desired behaviour.

type MyExecutor struct {
  Prefix string
}

// Options method is used as a helper to apply a bunch of options to executor
func (e *MyExecutor) Options(options ...execute.ExecuteOptions) {
  // apply all options to the executor
  for _, opt := range options {
    opt(e)
  }
}

// WithPrefix method is used to set the executor prefix attribute
func WithPrefix(prefix string) execute.ExecuteOptions {
  return func(e execute.Executor) {
    e.(*MyExecutor).Prefix = prefix
  }
}

func (e *MyExecutor) Execute(ctx context.Context, command []string, resultsFunc stdoutcallback.StdoutCallbackResultsFunc, options ...execute.ExecuteOptions) error {
  // It is possible to apply extra options when Execute is called
  for _, opt := range options {
    opt(e)
  }
  // that's a dummy work
  fmt.Println(fmt.Sprintf("[%s] %s\n", e.Prefix, "I am MyExecutor and I am doing nothing"))
  return nil
}

To execute the ansible-playbook command using the custom executor, you can use the following code:

// define an instance for the new executor and set the options
exe := &MyExecutor{}

exe.Options(
  WithPrefix("Go ansible example"),
)

playbook := &ansibler.AnsiblePlaybookCmd{
  Playbook:          "site.yml",
  ConnectionOptions: ansiblePlaybookConnectionOptions,
  Options:           ansiblePlaybookOptions,
  Exec:              exe,
}

playbook.Run(context.TODO())

When you run the playbook using your dummy executor, the output will be as follows:

go run myexecutor-ansibleplaybook.go
[Go ansible example] I am MyExecutor and I am doing nothing
Measurements

To facilitate taking measurements, go-ansible provides the github.com/apenella/go-ansible/pkg/execute/measure package. This package includes an ExecutorTimeMeasurement that acts as an Executor decorator, allowing you to measure the execution time that takes to finish either ansible or ansible-playbook commands.

To use the time measurement feature, you need to create an instance of ExecutorTimeMeasurement:

executorTimeMeasurement := measure.NewExecutorTimeMeasurement(
    execute.NewDefaultExecute(),
  )

Next, pass the created ExecutorTimeMeasurement as the Exec attribute value to either AnsiblePlaybookCmd or AnsibleAdhocCmd:

playbook := &playbook.AnsiblePlaybookCmd{
    Playbooks:         playbooksList,
    Exec:              executorTimeMeasurement,
    ConnectionOptions: ansiblePlaybookConnectionOptions,
    Options:           ansiblePlaybookOptions,
  }

For a detailed example showcasing how to use measurement, refer to the ansibleplaybook-time-measurement example in the `go-ansible repository.

Options

These options can be used to customize the behaviour of ansible and ansible-playbook commands executions. The go-ansible library provides types for defining command execution options in the github.com/apenella/go-ansible/pkg/options package.

Ansible ad-hoc and ansible-playbook Common Options
  • AnsibleConnectionOptions: This struct includes parameters described in the Connections Options section within the ansible or ansible-playbook's manual page. It defines how to connect to hosts when executing Ansible commands.
  • AnsiblePrivilegeEscalationOptions: This struct includes parameters described in the Escalation Options section within the ansible or ansible-playbook's manual page. It defines how to escalate privileges and become a user during ansible execution.

Stdout Callback

In go-ansible, you can define a specific stdout callback method by setting the StdoutCallback attribute on the AnsiblePlaybookCmd or AnsibleAdhocCmd structs. This allows you to customize the output of the commands. The output is managed by a function that adheres to the following signature:

// StdoutCallbackResultsFunc defines a function which manages ansible's stdout callbacks. The function expects a context, a reader that receives the data to be wrote and a writer that defines where to write the data coming from reader, Finally a list of transformers could be passed to update the output coming from the executor.
type StdoutCallbackResultsFunc func(context.Context, io.Reader, io.Writer, ...results.TransformerFunc) error

The functions to manage the output are defined in the github.com/apenella/go-ansible/pkg/stdoutcallback/results package. By utilizing these functions and defining a custom stdout callback, you can customize how the output of the execution.

Results

In the github.com/apenella/go-ansible/pkg/stdoutcallback/results package, there are different methods available to manage the outputs of Ansible commands.

Transformers

In go-ansible, a transformer is a function that enriches or updates the output received from the executor, according to your needs. The TransformerFunc type defines the signature of a transformer function:

// TransformerFunc is used to enrich or update messages before to be printed out
type TransformerFunc func(string) string

When the output is received from the executor, it is processed line by line, and the transformers are applied to each line. The github.com/apenella/go-ansible/pkg/stdoutcallback/results package provides a set of ready-to-use transformers, but you can also write custom transformers and set them through the executor.

Here are some examples of transformers available in the results package:

  • Prepend: Adds a prefix string to each output line.
  • Append: Adds a suffix string to each output line.
  • LogFormat: Includes a date-time prefix to each output line.
  • IgnoreMessage: Ignores output lines based on the patterns provided as input parameters.
Default

By default, the stdout callback results are managed by the DefaultStdoutCallbackResults method. This method handles the output by prepending the separator string ── to each line when no transformer is defined. It also prepares all the transformers before invoking the worker function responsible for writing the output to the io.Writer.

The DefaultStdoutCallbackResults method ensures that the output is formatted correctly and provides a basic handling of the results when no specific transformers are applied.

JSON

When the stdout callback method is set to JSON format, the output is managed by the JSONStdoutCallbackResults method. This method prepares the worker output function to use the IgnoreMessage transformer, which ignores any non-JSON lines. Other transformers are ignored, except for those specific to JSONStdoutCallbackResults.

Within the JSONStdoutCallbackResults function, there is an array called skipPatterns that contains matching expressions for lines that should be ignored. These patterns are used to skip specific lines that may not be relevant to the JSON output.

Here is an example of the skipPatterns array:

skipPatterns := []string{
    // This pattern skips timer's callback whitelist output
    "^[\\s\\t]*Playbook run took [0-9]+ days, [0-9]+ hours, [0-9]+ minutes, [0-9]+ seconds$",
  }
Manage JSON Output

The JSONStdoutCallbackResults method writes the JSON output to the provided io.Writer parameter. The github.com/apenella/go-ansible/pkg/stdoutcallback/results package includes the ParseJSONResultsStream function, which can be used to decode the JSON output into an AnsiblePlaybookJSONResults data structure. You can manipulate this data structure to format the JSON output according to your specific needs.

The expected JSON schema from ansible-playbook is defined here file within the Ansible repository.

Vault

The github.com/apenella/go-ansible/pkg/vault package provides functionality to encrypt variables. It introduces the VariableVaulter struct, which is responsible for creating a VaultVariableValue from the value that you need to encrypt.

The VaultVariableValue can return the instantiated variable in JSON format.

To perform the encryption, the vault package relies on an Encrypter interface implementation.

type Encrypter interface {
  Encrypt(plainText string) (string, error)
}

The encryption functionality is implemented in the encrypt package, which is described in the following section.

Encrypt

The github.com/apenella/go-ansible/pkg/vault/encrypt package is responsible for encrypting variables. It implements the Encrypter interface defined in the github.com/apenella/go-ansible/pkg/vault package.

Currently, the package provides the EncryptString struct, which supports the encryption of string variables. It utilizes the github.com/sosedoff/ansible-vault-go library for encryption.

To use the EncryptString struct, you need to instantiate it with a password reader. The password reader is responsible for providing the password used for encryption and it should implement the PasswordReader interface:

type PasswordReader interface {
  Read() (string, error)
}

Here's an example of how to instantiate the EncryptString struct:

encrypt := NewEncryptString(
  WithReader(
    text.NewReadPasswordFromText(
      text.WithText("secret"),
    ),
  ),
)

In this example, the text.NewReadPasswordFromText function is used to create a password reader that reads the password from a text source. The WithText option is used to specify the actual password value.

Password

The go-ansible library provides a set of packages that can be used as PasswordReader to read the password for encryption. The following sections describe these packages and how they can be used.

Envvars

The github.com/apenella/go-ansible/pkg/vault/password/envvars package allows you to read the password from an environment variable. To use this package, you need to use the NewReadPasswordFromEnvVar function and provide the name of the environment variable where the password is stored using the WithEnvVar option:

reader := NewReadPasswordFromEnvVar(
  WithEnvVar("VAULT_PASSWORD"),
)

In this example, the VAULT_PASSWORD environment variable is specified as the source of the password. The NewReadPasswordFromEnvVar function creates a password reader that reads the password from the specified environment variable.

Using the envvars package, you can conveniently read the password from an environment variable and use it for encryption.

File

The github.com/apenella/go-ansible/pkg/vault/password/file package allows you to read the password from a file, using the afero file system abstraction.

To use this package, you need to instantiate the NewReadPasswordFromFile function and provide the necessary options. The WithFs option is used to specify the file system, and the WithFile option is used to specify the path to the password file.

If you don't explicitly define a file system, the package uses the default file system, which is the OsFs from the github.com/spf13/afero package. The OsFs represents the file system of your host machine.

Therefore, if you don't provide a specific file system using the WithFs option when instantiating the password reader, the file package will automatically use the OsFs as the file system to read the password from a file.

Here's an example without specifying the file system:

reader := NewReadPasswordFromFile(
  WithFile("/password"),
)

In this case, the OsFs will be used to access the /password file on your host file system.

Resolve

The github.com/apenella/go-ansible/pkg/vault/password/resolve package provides a mechanism to resolve the password by exploring multiple PasswordReader implementations. It returns the first password obtained from any of the PasswordReader instances.

To use this package, you need to create a NewReadPasswordResolve instance and provide a list of PasswordReader implementations as arguments to the WithReader option:

reader := NewReadPasswordResolve(
  WithReader(
    envvars.NewReadPasswordFromEnvVar(
      envvars.WithEnvVar("VAULT_PASSWORD"),
    ),
    file.NewReadPasswordFromFile(
      file.WithFs(testFs),
      file.WithFile("/password"),
    ),
  ),
)

In this example, the ReadPasswordResolve instance is created with two PasswordReader implementations: one that reads the password from an environment variable (envvars.NewReadPasswordFromEnvVar), and another that reads the password from a file (file.NewReadPasswordFromFile).

The ReadPasswordResolve will attempt to obtain the password from each PasswordReader in the provided order. The first successful password read will be returned. It returns an error when no password is achieved.

Using the resolve package, you can explore multiple PasswordReader implementations to resolve the password for encryption.

Text

The github.com/apenella/go-ansible/pkg/vault/password/text package provides functionality to read the password from a text source.

To use this package, you need to instantiate the NewReadPasswordFromText function and provide the password as a text value using the WithText option:

reader := NewReadPasswordFromText(
  WithText("ThatIsAPassword"),
)

In this example, the password is directly specified as the text value "ThatIsAPassword" using the WithText option.

Examples

The go-ansible library includes a variety of examples that demonstrate how to use the library in different scenarios. These examples can be found in the examples directory of the go-ansible repository.

The examples cover various use cases and provide practical demonstrations of utilizing different features and functionalities offered by go-ansible. They serve as a valuable resource to understand and learn how to integrate go-ansible into your applications.

Feel free to explore the examples directory to gain insights and ideas on how to leverage the go-ansible library in your projects.

Here you have a list of examples:

Contributing

Thank you for your interest in contributing to go-ansible! All contributions are welcome, whether they are bug reports, feature requests, or code contributions. Please read the contributor's guide here to know more about how to contribute.

Code Of Conduct

The go-ansible project is committed to providing a friendly, safe and welcoming environment for all, regardless of gender, sexual orientation, disability, ethnicity, religion, or similar personal characteristic.

We expect all contributors, users, and community members to follow this code of conduct. This includes all interactions within the go-ansible community, whether online, in person, or otherwise.

Please to know more about the code of conduct refer here.

License

The go-ansible library is available under MIT license.

About

Go-ansible is a Go package that enables the execution of ansible-playbook or ansible commands directly from Golang applications. It supports a wide range of options for each command, enabling smooth integration of Ansible functionality into your projects.

Topics

Resources

License

Code of conduct

Stars

Watchers

Forks