go-ansible

repository·master·Indexed 22 days ago

https://github.com/apenella/go-ansible

A Go library that wraps the Ansible CLI, enabling Go applications to execute commands such as ansible-playbook, ansible-galaxy, ansible-inventory, and ansible. It provides an abstraction layer for managing playbooks, variables, and Ansible Vault secrets with fine-grained control over configuration and output. The library supports custom executors for Docker integration, output transformers, and timeout management.

Tokens
18.4K
Snippets
66
Records
89
Agent score
77%

What's inside go-ansible

  1. Overview of go-ansible

    master
    go-ansible is a Go library designed to execute Ansible commands (such as ansible-playbook, ansible-inventory, ansible-galaxy, and ansible) directly from Go applications. It acts as a wrapper around the Ansible CLI, providing an abstraction layer that allows you to manage complex playbooks and variables without reimplementing Ansible's core logic. It is suitable for workflow orchestration, custom configuration via Go code, and managing Ansible Vault secrets.
  2. Abstract command execution with the Exec package

    master

    The github.com/apenella/go-ansible/v2/pkg/execute/exec package provides an abstraction over os/exec.

    • Cmder interface: Defines methods that replicate os/exec.Cmd (e.g., Run(), Output(), StdoutPipe(), Wait()). This allows for customizing execution and mocking commands in tests.
    • OsExec struct: Replicates os/exec.Command and os/exec.CommandContext but returns a Cmder interface instead of a concrete *os/exec.Cmd.
    type Cmder interface {
      CombinedOutput() ([]byte, error)
      Environ() []string
      Output() ([]byte, error)
      Run() error
      Start() error
      StderrPipe() (io.ReadCloser, error)
      StdinPipe() (io.WriteCloser, error)
      StdoutPipe() (io.ReadCloser, error)
      String() string
      Wait() error
    }
  3. Replace the options argument in Executor with struct configuration

    master

    The options argument has been removed from the Execute method. You can no longer overwrite Executor attributes during the execution call.

    To configure your executor, you must establish all necessary settings during the instantiation of the struct. The Execute method should then run the command using these predefined settings.

  4. Replace the resultsFunc argument in Executor with ResultsOutputer

    master

    The resultsFunc argument has been removed from Execute. You must now include a ResultsOutputer attribute in your executor to handle command output.

    go-ansible provides two primary implementations of ResultsOutputer:

    • github.com/apenella/go-ansible/v2/pkg/execute/result/default.DefaultResults: Handles Ansible results as plain text.
    • github.com/apenella/go-ansible/v2/pkg/execute/json.JSONStdoutCallbackResults: Handles Ansible results in JSON format (use this if you are using a JSON stdout callback plugin).

    Your executor should use this attribute to print or process the results obtained from the command execution.

  5. Customize output with Transformer functions

    master

    A TransformerFunc is a function used to enrich or modify output messages line-by-line before they are printed. It follows this signature:

    type TransformerFunc func(string) string

    Transformers can be applied to DefaultResults, JSONLEventStdoutCallbackResults, or JSONStdoutCallbackResults. The github.com/apenella/go-ansible/v2/pkg/execute/result/transformer package provides several ready-to-use functions.

  6. Encrypt variables using the Vault package

    master

    The vault package provides tools to encrypt variables for Ansible Vault. The core component is the VariableVaulter, which uses an Encrypter implementation to create VaultVariableValue objects (which can be returned in JSON format).

    Currently, the encrypt package provides EncryptString, which implements the Encrypter interface using the github.com/sosedoff/ansible-vault-go library. EncryptString requires a PasswordReader to provide the encryption password.

    type Encrypter interface {
      Encrypt(plainText string) (string, error)
    }
  7. Configure Ansible stdout callback methods

    master

    The github.com/apenella/go-ansible/v2/pkg/execute/stdoutcallback package allows you to manage Ansible's stdout callback method. This involves setting the ANSIBLE_STDOUT_CALLBACK environment variable and defining how results are handled.

    Interfaces

    • ExecutorStdoutCallbackSetter: Extends Executor to allow adding environment variables (AddEnvVar) and specifying output handling (WithOutput).
    • ExecutorQuietStdoutCallbackSetter: Extends ExecutorStdoutCallbackSetter with a Quiet() method to silence command execution output. This is required by JSONStdoutCallbackExecute.

    Available Callback Structs

    Use these structs as decorators over an ExecutorStdoutCallbackSetter to apply specific callback plugins:

    • AnsiblePosixJsonlStdoutCallbackExecute
    • DebugStdoutCallbackExecute
    • DefaultStdoutCallbackExecute
    • DenseStdoutCallbackExecute
    • JSONStdoutCallbackExecute
    • MinimalStdoutCallbackExecute
    • NullStdoutCallbackExecute
    • OnelineStdoutCallbackExecute
    • StderrStdoutCallbackExecute
    • TimerStdoutCallbackExecute
    • YamlStdoutCallbackExecute
    execJson := stdoutcallback.NewJSONStdoutCallbackExecute(
      execute.NewDefaultExecute(
        execute.WithCmd(playbookCmd),
      ),
    )
    
    err := execJson.Execute(context.Background())
    if err != nil {
      // Manage the error
    }
  8. How the core execution components work together

    master

    The library's execution model relies on three main abstractions:

    • Command Generator (Commander): An interface responsible for generating the specific Ansible CLI command string (e.g., AnsiblePlaybookCmd).
    • Executor: The component that actually runs the command. The DefaultExecute implementation is the standard way to run commands. It takes a Commander and manages the lifecycle of the process.
    • Results Handler: A mechanism to capture and process the command's output. By default, DefaultExecute uses DefaultResults to handle output as plain text, but you can extend this to handle JSON or JSONL formats.
  9. Replace the command argument in Executor with Commander

    master

    The Executor no longer receives the command as an argument. Instead, it must hold an attribute of type Commander.

    1. Add a Commander attribute to your executor struct.
    2. Use the Commander.Command() method to retrieve the []string representing the command to execute.
    3. Pass that command to an Executabler component.

    Both AnsiblePlaybookCmd and AnsibleAdhocCmd implement the Commander interface.

  10. Core concepts of go-ansible

    master

    Understanding the relationship between these three core abstractions is essential for using the library:

    1. Command Generator (Commander): Responsible for generating the specific command to be executed. Examples include AnsiblePlaybookCmd and AnsibleAdhocCmd (introduced in v2.0.0).
    2. Executor: A component that executes the generated command and handles the execution output. The library provides a DefaultExecute implementation, but you can implement a custom executor if needed.
    3. Results Handler (Results Outputer): Responsible for managing the output of the command execution. Common implementations include DefaultResults and JSONStdoutCallbackResults.
  11. Implement a custom Docker executor using Executabler and Cmder

    master

    To redirect go-ansible commands to Docker, you must implement two core interfaces:

    1. Executabler: Implement CommandContext(ctx context.Context, name string, arg ...string) exec.Cmder. This method is responsible for returning a Cmder instance configured with the command name and arguments.
    2. Cmder: Implement the logic to manage the Docker container lifecycle. This includes:
      • Building the required image (imageBuild).
      • Creating the container (ContainerCreate) with the command and environment variables.
      • Attaching to the container output (ContainerAttach).
      • Starting the container (ContainerStart).

    To handle output, use stdcopy.StdCopy to demultiplex Docker's combined stdout/stderr stream into separate Go pipes.

    // Example of implementing CommandContext in a DockerExec struct
    func (e *DockerExec) CommandContext(ctx context.Context, name string, arg ...string) exec.Cmder {
        cmd := NewDockerCmd(e.client)
        cmd.ContainerName = "ansible_playbook_executor"
        cmd.Env = append([]string{}, e.Env...)
        cmd.Cmd = append([]string{}, name)
        cmd.Cmd = append(cmd.Cmd, arg...)
        return cmd
    }
  12. How Ansible Playbook Embed Python works

    master

    This pattern combines go-ansible with go-embed-python to create a portable Go binary that carries its own execution environment.

    Execution Lifecycle:

    1. Unpacking: When the application runs, it unpacks the embedded Ansible playbook from the Go binary into a temporary directory.
    2. Environment Setup: The embedded Python packages are also unpacked into a separate temporary directory.
    3. Execution: The playbook is executed using the unpacked embedded Python interpreter.

    Note: This is an architectural pattern/inspiration and not a native feature of the go-ansible library itself.