Implement Installation Logic (Issue 1-1-4)
Overview
Implement the installation logic for all 4 dependencies (cargo-machete, OpenTofu, Ansible, LXD) and add the install command to the CLI. This completes the dependency installer package by converting bash installation scripts to Rust with structured logging for automation and CI/CD integration.
Design Philosophy: Uses structured logging only (tracing crate) - no user-facing println!() output. Designed for automation and CI/CD pipelines.
Parent Issue
#113 - Create Dependency Installation Package for E2E Tests
Dependencies
Depends On:
- #116 - Create Docker Test Infrastructure (Issue 1-1-3)
Blocks:
- Issue 1-2: Integrate dependency-installer with E2E tests
Objectives
Key Components
DependencyInstaller Trait
#[async_trait]
pub trait DependencyInstaller: Send + Sync {
fn name(&self) -> &str;
fn dependency(&self) -> Dependency;
async fn install(&self) -> Result<(), InstallationError>;
fn requires_sudo(&self) -> bool { false }
}
Installer Implementations
Convert bash scripts to Rust:
-
CargoMacheteInstaller (scripts/setup/install-cargo-machete.sh)
- Uses
cargo install cargo-machete
- No sudo required
-
OpenTofuInstaller (scripts/setup/install-opentofu.sh)
- Downloads installer script with curl
- Runs with sudo
- Multi-step: download → chmod → execute → cleanup
-
AnsibleInstaller (scripts/setup/install-ansible.sh)
- Uses apt-get package manager
- Requires sudo
-
LxdInstaller (scripts/setup/install-lxd.sh)
- Uses snap for installation
- Configures user groups
- Requires sudo
Extended DependencyManager
Add installation methods to existing manager:
impl DependencyManager {
pub fn get_installer(&self, dep: Dependency) -> Box<dyn DependencyInstaller>;
pub async fn install(&self, dep: Dependency) -> Result<(), InstallationError>;
pub async fn install_all(&self) -> Result<Vec<InstallResult>, InstallationError>;
}
Install Command Handler
New handler following existing pattern:
// src/handlers/install.rs
pub async fn handle_install(
manager: &DependencyManager,
dependency: Option<Dependency>,
) -> Result<(), InstallError> {
// Handler implementation with structured logging
}
CLI Command
Add to existing CLI:
# Install all dependencies
dependency-installer install
# Install specific dependency
dependency-installer install --dependency opentofu
# With verbose logging
dependency-installer install --verbose
Docker Tests
Extend testing to verify actual installations:
// tests/docker_install_command.rs
#[tokio::test]
async fn test_install_cargo_machete() {
// Verify installation in clean container
}
#[tokio::test]
async fn test_install_idempotent() {
// Install twice, both should succeed
}
#[tokio::test]
async fn test_install_all() {
// Install all dependencies
}
Architecture
Directory Structure
packages/dependency-installer/
├── src/
│ ├── manager.rs # Add installation methods
│ ├── detector/ # Existing detection logic
│ ├── installer/ # NEW: Installation logic
│ │ ├── mod.rs # Trait + error types
│ │ ├── cargo_machete.rs
│ │ ├── opentofu.rs
│ │ ├── ansible.rs
│ │ └── lxd.rs
│ ├── handlers/ # Extend with install
│ │ ├── check.rs # Existing
│ │ ├── list.rs # Existing
│ │ └── install.rs # NEW
│ └── cli.rs # Add Install command
└── tests/
└── docker_install_command.rs # NEW tests
Handler-Based Architecture
Following existing pattern:
// src/app.rs
match cli.command {
Commands::Check { dependency } => {
check::handle_check(&manager, dependency)?;
}
Commands::List => {
list::handle_list(&manager)?;
}
Commands::Install { dependency } => { // NEW
install::handle_install(&manager, dependency).await?;
}
}
Structured Logging Examples
All output uses tracing crate - no println!() statements:
Installing All Dependencies
$ dependency-installer install
2025-11-04T10:15:20Z INFO install: Installing all dependencies
2025-11-04T10:15:21Z INFO install: Installing dependency dependency="cargo-machete"
2025-11-04T10:15:25Z INFO install: Installation successful dependency="cargo-machete" status="installed"
2025-11-04T10:15:26Z INFO install: Installing dependency dependency="OpenTofu"
2025-11-04T10:15:35Z INFO install: Installation successful dependency="OpenTofu" status="installed"
...
2025-11-04T10:15:56Z INFO install: All dependencies installed successfully
Installing Specific Dependency with Verbose Logging
$ dependency-installer install --dependency opentofu --verbose
2025-11-04T10:25:10Z INFO install: Installing specific dependency dependency="opentofu"
2025-11-04T10:25:11Z DEBUG opentofu_installer: Downloading installer script
2025-11-04T10:25:13Z DEBUG opentofu_installer: Making script executable
2025-11-04T10:25:14Z DEBUG opentofu_installer: Running installer with sudo
2025-11-04T10:25:20Z DEBUG opentofu_installer: Cleaning up installer script
2025-11-04T10:25:20Z INFO install: Installation complete dependency="opentofu" status="installed"
Controlling Log Output
# Default (INFO and above)
dependency-installer install
# Verbose (DEBUG and above)
dependency-installer install --verbose
# Specific level
dependency-installer install --log-level trace
# Environment variable
RUST_LOG=debug dependency-installer install
Exit Codes
The CLI uses consistent exit codes for automation:
- 0: Success (all installations succeeded)
- 1: Installation failures (one or more dependencies failed)
- 2: Invalid arguments (e.g., unknown dependency name)
- 3: Internal error (unexpected failure)
Example:
$ dependency-installer install --dependency opentofu
$ echo $?
0 # Success
$ dependency-installer install --dependency nonexistent
Error: Invalid value 'nonexistent' for '--dependency <DEPENDENCY>'
$ echo $?
2 # Invalid argument
Implementation Tasks
Phase 1: Installer Trait and Error Types
Phase 2: Convert Bash Scripts to Rust
Phase 3: Update DependencyManager
Phase 4: Add Install Command Handler
Phase 5: Update CLI and App
Phase 6: Docker Test Infrastructure
Phase 7: Testing and Validation
Phase 8: Documentation
Acceptance Criteria
DependencyInstaller Trait:
Installer Implementations:
CLI Install Command:
Docker Tests:
Quality:
Related Documentation
Notes
- Time Estimate: 4-5 hours (largest of the 4 phases)
- Design Pattern: Two-trait design (DependencyDetector + DependencyInstaller)
- Automation Focus: Structured logging only, no user interaction
- Idempotent: All installers handle repeated runs safely
- Docker Testing: Required to ensure installations work in clean environments
Next Steps After Completion
- Dependency-installer package is complete
- Issue 1-2: Integrate with E2E tests
- Issue 1-3: Update CI workflows to use binary instead of bash scripts
Implement Installation Logic (Issue 1-1-4)
Overview
Implement the installation logic for all 4 dependencies (cargo-machete, OpenTofu, Ansible, LXD) and add the
installcommand to the CLI. This completes the dependency installer package by converting bash installation scripts to Rust with structured logging for automation and CI/CD integration.Design Philosophy: Uses structured logging only (tracing crate) - no user-facing
println!()output. Designed for automation and CI/CD pipelines.Parent Issue
#113 - Create Dependency Installation Package for E2E Tests
Dependencies
Depends On:
Blocks:
Objectives
DependencyInstallertrait for installation abstractionscripts/setup/) to Rust installer implementationsinstallcommand to CLI binary with handler-based architectureDependencyManagerto coordinate installationKey Components
DependencyInstaller Trait
Installer Implementations
Convert bash scripts to Rust:
CargoMacheteInstaller (
scripts/setup/install-cargo-machete.sh)cargo install cargo-macheteOpenTofuInstaller (
scripts/setup/install-opentofu.sh)AnsibleInstaller (
scripts/setup/install-ansible.sh)LxdInstaller (
scripts/setup/install-lxd.sh)Extended DependencyManager
Add installation methods to existing manager:
Install Command Handler
New handler following existing pattern:
CLI Command
Add to existing CLI:
Docker Tests
Extend testing to verify actual installations:
Architecture
Directory Structure
Handler-Based Architecture
Following existing pattern:
Structured Logging Examples
All output uses
tracingcrate - noprintln!()statements:Installing All Dependencies
Installing Specific Dependency with Verbose Logging
Controlling Log Output
Exit Codes
The CLI uses consistent exit codes for automation:
Example:
Implementation Tasks
Phase 1: Installer Trait and Error Types
src/installer/mod.rsDependencyInstallertrait with async methodsInstallationErrorenum with thiserrorPhase 2: Convert Bash Scripts to Rust
src/installer/cargo_machete.rssrc/installer/opentofu.rs(multi-step with curl)src/installer/ansible.rs(apt-get with sudo)src/installer/lxd.rs(snap with groups)InstallationErrorPhase 3: Update DependencyManager
get_installer()methodinstall()async methodinstall_all()async methodInstallResultstructPhase 4: Add Install Command Handler
src/handlers/install.rshandle_install()functionsrc/handlers/mod.rsPhase 5: Update CLI and App
src/cli.rswithInstallcommandsrc/app.rsto handle install commandPhase 6: Docker Test Infrastructure
tests/containers/ubuntu.rswith sudo supporttests/docker_install_command.rsPhase 7: Testing and Validation
Phase 8: Documentation
packages/dependency-installer/README.mdAcceptance Criteria
DependencyInstaller Trait:
InstallationErrorconsistentlyInstaller Implementations:
CLI Install Command:
Docker Tests:
Quality:
Related Documentation
Notes
Next Steps After Completion