This package provides dependency detection and installation utilities for the Torrust Tracker Deployer project.
This is an internal automation tool - Its primary purpose is to check dependencies in CI/CD pipelines and automated workflows. As such, it uses structured logging only (via the tracing crate) rather than user-facing console output.
This design choice offers several benefits:
- Automation-friendly: Exit codes provide reliable success/failure indication
- Consistent: Same output format as the rest of the Torrust ecosystem
- Simple: The tool is straightforward enough that logging output is sufficient even for manual use
- Observable: Rich contextual information through structured fields
Always use exit codes to determine success or failure:
- ✅ DO check exit codes (0 = success, non-zero = failure)
- ✅ DO use
--log-level offto suppress output in scripts - ❌ DON'T parse log output - it's not a stable API and may change
For manual usage, you can control log verbosity with the --verbose flag or RUST_LOG environment variable.
- Dependency Detection: Check if required development tools are installed
- Dependency Installation: Install missing dependencies automatically
- Extensible: Easy to add new dependency detectors and installers
- Structured Logging: Built-in tracing support for observability and automation
- Type-Safe: Uses strongly-typed enums for dependencies
- Error Handling: Clear, actionable error messages
- Async Support: Asynchronous installation operations for better performance
This package can detect and install the following development dependencies:
- cargo-machete - Detects unused Rust dependencies
- OpenTofu - Infrastructure provisioning tool
- Ansible - Configuration management tool
- LXD - VM-based testing infrastructure
When running in GitHub Copilot agent environment, network access to external domains is restricted by a firewall. The OpenTofu installer requires opentofu.org to be added to the custom allowlist. See Copilot Agent Firewall Configuration for details on configured domains and setup instructions.
The package provides a dependency-installer binary for command-line usage:
# Check all dependencies (default: info log level)
dependency-installer check
# Check specific dependency
dependency-installer check --dependency opentofu
# Install all dependencies
dependency-installer install
# Install specific dependency
dependency-installer install --dependency opentofu
# List all dependencies with status
dependency-installer list
# Control log level (off, error, warn, info, debug, trace)
dependency-installer check --log-level debug
dependency-installer install --log-level debug
dependency-installer check --log-level off # Disable all logging
# Enable verbose logging (equivalent to --log-level debug)
dependency-installer check --verbose
dependency-installer install --verbose
# Get help
dependency-installer --help
dependency-installer check --help
dependency-installer install --helpExit codes are the official way to determine command success or failure. Scripts and automation tools should rely on exit codes, not parse the logging output.
- 0: Success (all checks or installations passed)
- 1: Missing dependencies or installation failures
- 2: Invalid arguments
- 3: Internal error
For automation:
# ✅ DO: Check exit code for success/failure
if dependency-installer check --log-level off; then
echo "All dependencies installed"
else
echo "Missing dependencies (exit code: $?)"
fi
# ✅ DO: Use exit code in CI/CD pipelines
dependency-installer install --log-level off
if [ $? -eq 0 ]; then
echo "Installation succeeded"
# Continue with next steps
fi
# ❌ DON'T: Parse log output - it may change and is not stable API
# output=$(dependency-installer check)
# if echo "$output" | grep -q "installed"; then # FRAGILE - don't do thisThe tool uses structured logging (via tracing) for observability and debugging.
This output is designed for human reading, not for parsing by scripts.
Important: Logging format may change in future versions. Always use exit codes for automation.
# Check all dependencies (default log level shows INFO and above)
$ dependency-installer check
2025-11-04T17:33:20.959847Z INFO torrust_dependency_installer::handlers::check: Checking all dependencies
2025-11-04T17:33:20.960126Z INFO torrust_dependency_installer::handlers::check: Dependency check result dependency="cargo-machete" status="installed"
2025-11-04T17:33:20.960131Z INFO torrust_dependency_installer::handlers::check: Dependency check result dependency="OpenTofu" status="not installed"
2025-11-04T17:33:20.960136Z INFO torrust_dependency_installer::handlers::check: Dependency check result dependency="Ansible" status="not installed"
2025-11-04T17:33:20.960139Z INFO torrust_dependency_installer::handlers::check: Dependency check result dependency="LXD" status="installed"
2025-11-04T17:33:20.960144Z INFO torrust_dependency_installer::handlers::check: Missing dependencies missing_count=2 total_count=4
Error: Check command failed: Failed to check all dependencies: Missing 2 out of 4 required dependencies
# Check specific dependency
$ dependency-installer check --dependency opentofu
2025-11-04T17:33:20.959855Z INFO torrust_dependency_installer::handlers::check: Checking specific dependency dependency=opentofu
2025-11-04T17:33:20.960473Z INFO torrust_dependency_installer::detector::opentofu: OpenTofu is not installed dependency="opentofu"
2025-11-04T17:33:20.960482Z INFO torrust_dependency_installer::handlers::check: Dependency is not installed dependency="OpenTofu" status="not installed"
Error: Check command failed: Failed to check specific dependency: opentofu: not installed
# Install all dependencies
$ dependency-installer install
2025-11-04T19:30:10.000000Z INFO torrust_dependency_installer::handlers::install: Installing all dependencies
2025-11-04T19:30:10.100000Z INFO torrust_dependency_installer::installer::cargo_machete: Installing cargo-machete dependency="cargo-machete"
2025-11-04T19:30:25.000000Z INFO torrust_dependency_installer::handlers::install: Dependency installation result dependency="cargo-machete" status="installed"
2025-11-04T19:30:25.100000Z INFO torrust_dependency_installer::installer::opentofu: Installing OpenTofu dependency="opentofu"
2025-11-04T19:30:40.000000Z INFO torrust_dependency_installer::handlers::install: Dependency installation result dependency="OpenTofu" status="installed"
...
2025-11-04T19:31:00.000000Z INFO torrust_dependency_installer::handlers::install: All dependencies installed successfully
# Install specific dependency with verbose logging
$ dependency-installer install --dependency opentofu --verbose
2025-11-04T19:30:10.000000Z INFO torrust_dependency_installer::handlers::install: Installing specific dependency dependency=opentofu
2025-11-04T19:30:10.100000Z INFO torrust_dependency_installer::installer::opentofu: Installing OpenTofu dependency="opentofu"
2025-11-04T19:30:10.200000Z DEBUG torrust_dependency_installer::installer::opentofu: Downloading OpenTofu installer script
2025-11-04T19:30:12.000000Z DEBUG torrust_dependency_installer::installer::opentofu: Making installer script executable
2025-11-04T19:30:12.100000Z DEBUG torrust_dependency_installer::installer::opentofu: Running OpenTofu installer with sudo
2025-11-04T19:30:25.000000Z DEBUG torrust_dependency_installer::installer::opentofu: Cleaning up installer script
2025-11-04T19:30:25.100000Z INFO torrust_dependency_installer::handlers::install: Dependency installation completed dependency="OpenTofu" status="installed"
# List all dependencies
$ dependency-installer list
2025-11-04T17:33:20.960482Z INFO torrust_dependency_installer::handlers::list: Available dependency dependency="cargo-machete" status="installed"
2025-11-04T17:33:20.960494Z INFO torrust_dependency_installer::handlers::list: Available dependency dependency="OpenTofu" status="not installed"
2025-11-04T17:33:20.960962Z INFO torrust_dependency_installer::handlers::list: Available dependency dependency="Ansible" status="not installed"
2025-11-04T17:33:20.961521Z INFO torrust_dependency_installer::handlers::list: Available dependency dependency="LXD" status="installed"
# Enable verbose logging (includes DEBUG level)
$ dependency-installer check --verbose
2025-11-04T17:33:20.959872Z DEBUG torrust_dependency_installer::detector::cargo_machete: Checking if cargo-machete is installed dependency="cargo-machete"
...The CLI accepts the following dependency names:
cargo-machete- Rust dependency analyzeropentofu- Infrastructure provisioning toolansible- Configuration management toollxd- Lightweight VM manager
use torrust_dependency_installer::DependencyManager;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Initialize tracing for structured logging
tracing_subscriber::fmt::init();
let manager = DependencyManager::new();
// Check all dependencies
let results = manager.check_all()?;
for result in results {
let detector = manager.get_detector(result.dependency);
tracing::info!(
dependency = detector.name(),
installed = result.installed,
"Dependency status"
);
}
Ok(())
}use torrust_dependency_installer::{Dependency, DependencyManager};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Initialize tracing for structured logging
tracing_subscriber::fmt::init();
let manager = DependencyManager::new();
// Install specific dependency
manager.install(Dependency::OpenTofu).await?;
// Or install all dependencies
let results = manager.install_all().await;
for result in results {
let installer = manager.get_installer(result.dependency);
if result.success {
tracing::info!(
dependency = installer.name(),
"Installation succeeded"
);
} else {
tracing::error!(
dependency = installer.name(),
error = result.error.as_deref().unwrap_or("unknown"),
"Installation failed"
);
}
}
Ok(())
}use torrust_dependency_installer::{DependencyDetector, Dependency, DependencyManager};
fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt::init();
let manager = DependencyManager::new();
let detector = manager.get_detector(Dependency::OpenTofu);
if detector.is_installed()? {
tracing::info!(dependency = detector.name(), "Dependency is installed");
} else {
tracing::warn!(dependency = detector.name(), "Dependency is not installed");
}
Ok(())
}The Docker-based integration tests use a pre-configured image (docker/ubuntu-24.04.Dockerfile) that documents the operating system dependencies required before using the installers. These pre-conditions include:
- System packages:
ca-certificates,sudo,curl,build-essential - Rust toolchain:
nightly-2025-10-15via rustup (for cargo-machete installation) - PATH configuration: Cargo binaries accessible in PATH
The Dockerfile serves as explicit documentation of what must be installed on the operating system before the dependency installers can work. This ensures:
- Clear expectations for production environments
- Fast test execution (OS dependencies installed once during image build)
- Confidence that installers work given the declared pre-conditions
# Run all tests (unit tests run normally, Docker tests use pre-built image)
cargo test -p torrust-dependency-installer
# Run only Docker-based integration tests
cargo test -p torrust-dependency-installer --test docker_install_command
cargo test -p torrust-dependency-installer --test docker_check_command
# Run expensive tests (OpenTofu, Ansible, LXD)
cargo test -p torrust-dependency-installer -- --ignored
# Build the test Docker image
docker build -f docker/ubuntu-24.04.Dockerfile -t dependency-installer-test:ubuntu-24.04 .See docker/README.md for detailed testing infrastructure documentation.
Add to your Cargo.toml:
[dependencies]
torrust-dependency-installer = { path = "path/to/torrust-dependency-installer" }Or if using in a workspace:
[workspace]
members = ["packages/torrust-dependency-installer"]
[dependencies]
torrust-dependency-installer = { path = "packages/torrust-dependency-installer" }