-
Notifications
You must be signed in to change notification settings - Fork 0
Description
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
- Define
DependencyInstallertrait for installation abstraction - Convert bash scripts (
scripts/setup/) to Rust installer implementations - Add
installcommand to CLI binary with handler-based architecture - Extend
DependencyManagerto coordinate installation - Extend Docker tests to verify actual installations
- Use structured logging (tracing) throughout for observability
- Document exit codes and usage patterns
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
- Uses
-
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 --verboseDocker 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 successfullyInstalling 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 installExit 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 argumentImplementation Tasks
Phase 1: Installer Trait and Error Types
- Create
src/installer/mod.rs - Define
DependencyInstallertrait with async methods - Define
InstallationErrorenum with thiserror - Add module exports
Phase 2: Convert Bash Scripts to Rust
- Cargo-machete: Create
src/installer/cargo_machete.rs - OpenTofu: Create
src/installer/opentofu.rs(multi-step with curl) - Ansible: Create
src/installer/ansible.rs(apt-get with sudo) - LXD: Create
src/installer/lxd.rs(snap with groups) - Add structured logging to all installers
- Handle errors with
InstallationError
Phase 3: Update DependencyManager
- Add
get_installer()method - Implement
install()async method - Implement
install_all()async method - Define
InstallResultstruct
Phase 4: Add Install Command Handler
- Create
src/handlers/install.rs - Implement
handle_install()function - Implement helper functions
- Define handler error types
- Add structured logging
- Update
src/handlers/mod.rs
Phase 5: Update CLI and App
- Update
src/cli.rswithInstallcommand - Update
src/app.rsto handle install command - Ensure async support configured
- Update help text
Phase 6: Docker Test Infrastructure
- Update
tests/containers/ubuntu.rswith sudo support - Create
tests/docker_install_command.rs - Implement tests for each dependency
- Implement idempotency tests
- Implement install-all test
Phase 7: Testing and Validation
- Unit tests for installers
- Integration tests in Docker
- Manual testing
- Run complete test suite
- Pre-commit checks pass
Phase 8: Documentation
- Update
packages/dependency-installer/README.md - Document installer implementations
- Document exit codes
- Add usage examples
Acceptance Criteria
DependencyInstaller Trait:
- Trait defined with clear contract
- All 4 installers implement the trait
- Error handling uses
InstallationErrorconsistently - Structured logging provides observability
Installer Implementations:
- All installers work in Docker containers
- Installations are idempotent (can run multiple times)
- Sudo requirements correctly marked
- Verified with check command
- Clear, actionable error messages
CLI Install Command:
- Works for all dependencies individually
- Works for installing all dependencies
- Structured logging output is informative
- Exit codes are correct
- Help text is accurate
Docker Tests:
- Tests verify actual installation in clean containers
- Idempotency tests pass
- Verification with check command works
- All tests pass consistently
Quality:
- Pre-commit checks pass
- No clippy warnings
- Code properly formatted
- Documentation complete
Related Documentation
- Specification Document - Detailed technical specification
- Issue 1-1-3 - Docker infrastructure
- Issue 1-1-2 - CLI binary
- Issue 1-1-1 - Detection logic
- Parent Issue - Package overview
- Package README - Package 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