Ansible Executor
Playbook execution, inventory management, and SSH connection pooling.
Overview
The ansible package provides a pure Go implementation for executing Ansible-style playbooks. It parses YAML inventories, manages SSH connections with pooling, and returns structured results for each task.
Executor
The Executor is the primary entry point for running playbooks.
type Executor struct {
// Internal: basePath, inventory, vars, facts, results
}
Creating an Executor
import "forge.lthn.ai/core/go-devops/ansible"
// Create an executor rooted at the playbooks directory
executor := ansible.NewExecutor("/path/to/playbooks")
Loading Inventory
// Load inventory from a YAML file
executor.SetInventory("/path/to/inventory.yml")
Running a Playbook
ctx := context.Background()
result, err := executor.Run(ctx, "deploy.yml")
if err != nil {
log.Fatalf("playbook failed: %v", err)
}
// Check results
for _, taskResult := range result.Tasks {
fmt.Printf("Task: %s — Changed: %t, RC: %d\n",
taskResult.Name, taskResult.Changed, taskResult.RC)
}
Inventory
The Inventory type models Ansible's host group structure with per-host and per-group variables.
type Inventory struct {
Groups map[string]HostGroup // Named groups of hosts
Vars map[string]any // Global inventory variables
}
type HostGroup struct {
Hosts []Host // Hosts in this group
Vars map[string]any // Group-level variables
}
type Host struct {
Name string // Hostname or IP
Address string // Connection address
Port int // SSH port (default: 22)
User string // SSH user
Vars map[string]any // Host-level variables
}
Example Inventory
all:
vars:
ansible_user: deploy
children:
webservers:
hosts:
web1:
ansible_host: 10.0.1.10
ansible_port: 4819
web2:
ansible_host: 10.0.1.11
databases:
hosts:
db1:
ansible_host: 10.0.2.10
vars:
db_engine: postgresql
Plays and Tasks
Play
A play targets a group of hosts with a list of tasks.
type Play struct {
Name string // Play name
Hosts string // Target host group
Tasks []Task // Ordered task list
Vars map[string]any
}
Task
Each task executes a single module with arguments.
type Task struct {
Name string // Human-readable task name
Module string // Module to execute (e.g. "shell", "copy", "service")
Args map[string]any // Module arguments
Register string // Variable name to store result
Notify []string // Handlers to trigger on change
Tags []string // Tags for selective execution
}
TaskResult
Every task execution produces a structured result.
type TaskResult struct {
Name string // Task name
Changed bool // Whether the task made changes
Output string // stdout/stderr combined
RC int // Return code (0 = success)
}
Example: Checking Results
result, err := executor.Run(ctx, "provision.yml")
if err != nil {
log.Fatal(err)
}
for _, tr := range result.Tasks {
if tr.RC != 0 {
fmt.Printf("FAILED: %s (rc=%d)\nOutput: %s\n", tr.Name, tr.RC, tr.Output)
} else if tr.Changed {
fmt.Printf("CHANGED: %s\n", tr.Name)
} else {
fmt.Printf("OK: %s\n", tr.Name)
}
}
SSH Connection Pooling
The SSHClient manages persistent SSH connections to avoid repeated handshakes during playbook execution.
type SSHClient struct {
// Internal: connection pool, config, timeout
}
How Pooling Works
- On first connection to a host, the client establishes an SSH session
- Subsequent tasks reuse the existing connection
- Connections are automatically closed when the executor finishes
- Failed connections are evicted from the pool and re-established
This significantly reduces execution time for playbooks with many tasks per host, as the SSH handshake (key exchange, authentication) only happens once.
Connection Configuration
The SSH client reads connection parameters from the inventory:
| Inventory Variable | Purpose |
|---|---|
ansible_host |
Connection address |
ansible_port |
SSH port (default: 22) |
ansible_user |
SSH username |
ansible_ssh_private_key_file |
Path to private key |
ansible_ssh_common_args |
Additional SSH arguments |
Example: Full Workflow
import (
"forge.lthn.ai/core/go-devops/ansible"
"forge.lthn.ai/core/go-devops/build"
)
// Build the project first
builder := build.New(build.Config{
ProjectDir: "./my-service",
OutputDir: "./dist",
})
artifact, err := builder.Build(ctx)
// Then deploy with Ansible
executor := ansible.NewExecutor("./playbooks")
executor.SetInventory("./inventory/production.yml")
// Set extra vars including the build artefact path
executor.SetVar("artifact_path", artifact.Path)
executor.SetVar("service_version", "1.2.0")
result, err := executor.Run(ctx, "deploy.yml")
if err != nil {
log.Fatalf("deployment failed: %v", err)
}
fmt.Printf("Deployment complete: %d tasks, %d changed\n",
len(result.Tasks), result.ChangedCount())
See Also
- Home — Package overview
- Build-System — Building artefacts before deployment
- Infrastructure — Provisioning servers to deploy to