1 Ansible-Executor
Virgil edited this page 2026-02-19 17:00:43 +00:00

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

  1. On first connection to a host, the client establishes an SSH session
  2. Subsequent tasks reuse the existing connection
  3. Connections are automatically closed when the executor finishes
  4. 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