ansible-rs

Crates.ioansible-rs
lib.rsansible-rs
version1.1.0
created_at2025-07-12 13:30:44.812036+00
updated_at2025-07-12 13:30:44.812036+00
descriptionA Rust wrapper library for Ansible command-line tools (Linux/Unix only)
homepage
repositoryhttps://github.com/yourusername/ansible-rs
max_upload_size
id1749291
size595,046
doovemax (doovemax)

documentation

README

Ansible-rs

A modern Rust wrapper library for Ansible command-line tools, built with Rust 2024 edition.

中文文档 | English

Features

  • Type-safe - Leverages Rust's type system to prevent common configuration errors
  • Modern API - Uses builder patterns and fluent interfaces
  • Sync & Async Support - Full support for both synchronous and asynchronous operations
  • Concurrent Execution - Run multiple Ansible commands concurrently with async/await
  • Comprehensive error handling - Detailed error types for different failure modes
  • Memory efficient - Optimized for minimal allocations and clones
  • Rust 2024 edition - Uses the latest Rust features and idioms

Quick Start

Add this to your Cargo.toml:

[dependencies]
ansible = "1.0.0"

# For async support
ansible = { version = "1.0.0", features = ["async"] }

Synchronous Usage

use ansible::{Ansible, Module, Result};

fn main() -> Result<()> {
    let mut ansible = Ansible::default();
    ansible
        .set_system_envs()
        .add_host("all")
        .set_inventory("./hosts");

    // Execute a ping
    let result = ansible.ping()?;
    println!("Ping result: {}", result);

    // Execute a shell command
    let result = ansible.shell("uptime")?;
    println!("Uptime: {}", result);

    Ok(())
}

Asynchronous Usage

use ansible::{AsyncAnsible, Module};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut ansible = AsyncAnsible::new();
    ansible
        .set_system_envs()
        .add_host("all")
        .set_inventory("./hosts");

    // Execute a ping asynchronously
    let result = ansible.ping().await?;
    println!("Ping result: {}", result);

    // Execute a shell command asynchronously
    let result = ansible.shell("uptime").await?;
    println!("Uptime: {}", result);

    Ok(())
}

Concurrent Operations

use ansible::AsyncAnsible;
use tokio::try_join;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut ansible1 = AsyncAnsible::new();
    ansible1.add_host("web-servers");

    let mut ansible2 = AsyncAnsible::new();
    ansible2.add_host("db-servers");

    // Run commands concurrently
    let (web_result, db_result) = try_join!(
        ansible1.ping(),
        ansible2.ping()
    )?;

    println!("Web servers: {}", web_result);
    println!("DB servers: {}", db_result);

    Ok(())
}

Playbook Execution

Synchronous Playbooks

use ansible::{Playbook, Play, Result};

fn main() -> Result<()> {
    let mut playbook = Playbook::default();
    playbook.set_inventory("./hosts");

    // Run from file
    let result = playbook.run(Play::from_file("site.yml"))?;

    // Run from string content
    let yaml_content = r#"
    ---
    - hosts: all
      tasks:
        - name: Hello World
          debug:
            msg: "Hello from Rust!"
    "#;
    let result = playbook.run(Play::from_content(yaml_content))?;

    Ok(())
}

Asynchronous Playbooks

use ansible::{AsyncPlaybook, AsyncPlay};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut playbook = AsyncPlaybook::new();
    playbook.set_inventory("./hosts");

    // Run from file asynchronously
    let result = playbook.run(AsyncPlay::from_file("site.yml")).await?;

    // Run from string content asynchronously
    let yaml_content = r#"
    ---
    - hosts: all
      tasks:
        - name: Hello World
          debug:
            msg: "Hello from async Rust!"
    "#;
    let result = playbook.run(AsyncPlay::from_content(yaml_content)).await?;

    Ok(())
}

Sync to Async Conversion

use ansible::{Ansible, IntoAsync};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Create a synchronous instance
    let sync_ansible = Ansible::default();

    // Convert to async
    let async_ansible = sync_ansible.into_async();

    // Use async methods
    let result = async_ansible.ping().await?;
    println!("Result: {}", result);

    Ok(())
}

Async Features

Enabling Async Support

Add the async feature to your Cargo.toml:

[dependencies]
ansible = { version = "1.0.0", features = ["async"] }
tokio = { version = "1.0", features = ["full"] }

Available Async Types

  • AsyncAnsible - Asynchronous Ansible command execution
  • AsyncPlaybook - Asynchronous Playbook execution
  • AsyncAnsibleVault - Asynchronous Vault operations
  • AsyncAnsibleConfig - Asynchronous configuration management
  • AsyncAnsibleInventory - Asynchronous inventory management

Conversion Traits

  • IntoAsync - Convert sync types to async
  • FromAsync - Convert async types to sync
  • AsyncExecute - Unified async execution interface

Performance Benefits

  • Direct async implementation - No thread pool overhead
  • Concurrent execution - Run multiple operations simultaneously
  • Non-blocking I/O - Doesn't block the event loop
  • Memory efficient - Optimized async operations

Examples

Run the async demo:

cargo run --example async_demo --features async

Run async tests:

cargo test --features async test_async

Testing

This project includes a Docker-based testing environment with multiple target systems.

Prerequisites

  • Docker and Docker Compose
  • Ansible installed on your system
  • Rust 1.85+ (for Rust 2024 edition support)

Running Tests

Using cargo-nextest (Recommended)

Install cargo-nextest:

cargo install cargo-nextest

Run tests with nextest:

# Quick tests
cargo nextest run --profile quick

# Full test suite
cargo nextest run --all-features

# CI profile tests
cargo nextest run --profile ci

# Use our test script
./scripts/test-nextest.sh all

Traditional Testing

  1. Start the test environment:
./test-docker.sh
  1. Or manually:
# Start containers
docker-compose up -d

# Run examples
cargo run --example ansible
cargo run --example playbook

# Clean up
docker-compose down

Test Environment

The test environment includes:

  • Ubuntu 22.04 container (port 2222)
  • CentOS 8 container (port 2223)
  • Alpine Linux container (port 2224)

All containers are configured with:

  • SSH access (user: testuser, pass: testpass)
  • Python 3 for Ansible
  • Sudo access for the test user

API Documentation

Ansible Commands

let mut ansible = Ansible::default();

// Configuration
ansible.set_system_envs()           // Use system environment
       .filter_envs(["HOME", "PATH"]) // Filter specific env vars
       .add_host("web-servers")      // Add target hosts
       .set_inventory("./hosts")     // Set inventory file
       .arg("-v");                   // Add custom arguments

// Execute modules
ansible.ping()?;                    // Ping module
ansible.setup()?;                   // Setup/facts module  
ansible.shell("ls -la")?;           // Shell module
ansible.command("uptime")?;         // Command module
ansible.script("./script.sh")?;     // Script module

// Custom modules
let module = Module::other("package", "name=nginx state=present");
ansible.run(module)?;

Playbooks

let mut playbook = Playbook::default();

// Configuration  
playbook.set_inventory("./hosts")
        .set_output_json()           // JSON output format
        .arg("--check");             // Dry run mode

// Execute playbooks
playbook.run(Play::from_file("site.yml"))?;
playbook.run(Play::from_content(yaml_string))?;

Error Handling

The library provides comprehensive error types:

use ansible::{AnsibleError, Result};

match ansible.ping() {
    Ok(result) => println!("Success: {}", result),
    Err(AnsibleError::CommandFailed { message, exit_code, stdout, stderr }) => {
        eprintln!("Command failed: {}", message);
        eprintln!("Exit code: {:?}", exit_code);
        eprintln!("Stdout: {:?}", stdout);
        eprintln!("Stderr: {:?}", stderr);
    },
    Err(AnsibleError::InvalidInventory(msg)) => {
        eprintln!("Inventory error: {}", msg);
    },
    Err(e) => eprintln!("Other error: {}", e),
}

License

This project is licensed under the AGPL-3.0 License - see the LICENSE file for details.

Commit count: 0

cargo fmt