Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
c302577
Initial plan
Copilot Nov 4, 2025
b5af2fc
feat: [#116] add Docker test infrastructure for CLI
Copilot Nov 4, 2025
2040c38
style: [#116] fix rustfmt formatting issues
Copilot Nov 4, 2025
7432591
refactor: [#116] address code review feedback
Copilot Nov 4, 2025
a3b70c4
fix: [#116] address clippy linting errors
Copilot Nov 4, 2025
41690ea
refactor: [#116] improve container test utilities structure
josecelano Nov 4, 2025
98b1b9c
refactor: [#116] introduce CommandOutput type for container execution
josecelano Nov 4, 2025
8048ea8
refactor: [#116] introduce ExitCode type alias
josecelano Nov 4, 2025
7da8994
refactor: [#116] extract logging into dedicated module
josecelano Nov 4, 2025
7e461ed
refactor: [#116] extract handlers module with check and list submodules
josecelano Nov 4, 2025
36f9627
refactor: [#116] replace Box<dyn Error> with concrete error enums in …
josecelano Nov 4, 2025
7bc0b8f
refactor: [#116] eliminate map_err calls using From trait implementat…
josecelano Nov 4, 2025
2268d4b
refactor: [#116] use Dependency enum for type-safe dependency references
josecelano Nov 4, 2025
1a7c639
refactor: [#116] normalize 'tool' to 'dependency' terminology and use…
josecelano Nov 4, 2025
6c62e5f
refactor: [#116] simplify dependency-installer output to use structur…
josecelano Nov 4, 2025
7d9fefb
feat: [#116] add --log-level CLI option to control logging output
josecelano Nov 4, 2025
3572826
refactor: [#116] improve structured logging with consistent status field
josecelano Nov 4, 2025
194ce60
refactor: [#116] apply module organization conventions to dependency-…
josecelano Nov 4, 2025
0b0beda
test: [#116] rename tests to follow it_should pattern and reveal inte…
josecelano Nov 4, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions packages/dependency-installer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,11 @@ clap = { version = "4.0", features = [ "derive" ] }
thiserror = "1.0"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = [ "env-filter" ] }

[dev-dependencies]
testcontainers = "0.25"
tokio = { version = "1.0", features = [ "full" ] }

[[test]]
name = "docker_check_command"
path = "tests/docker_check_command.rs"
130 changes: 83 additions & 47 deletions packages/dependency-installer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,28 @@

This package provides dependency detection and installation utilities for the Torrust Tracker Deployer project.

## Design Philosophy

**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**: Structured logs are easy to parse and filter programmatically
- **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

For manual usage, you can control log verbosity with the `--verbose` flag or `RUST_LOG` environment variable.

## Features

- **Tool Detection**: Check if required development tools are installed
- **Extensible**: Easy to add new tool detectors
- **Logging**: Built-in tracing support for observability
- **Dependency Detection**: Check if required development tools are installed
- **Extensible**: Easy to add new dependency detectors
- **Structured Logging**: Built-in tracing support for observability and automation
- **Type-Safe**: Uses strongly-typed enums for dependencies
- **Error Handling**: Clear, actionable error messages

## Required Tools
## Supported Dependencies

This package can detect the following development dependencies:

Expand All @@ -25,16 +39,20 @@ This package can detect the following development dependencies:
The package provides a `dependency-installer` binary for command-line usage:

```bash
# Check all dependencies
# Check all dependencies (default: info log level)
dependency-installer check

# Check specific tool
dependency-installer check --tool opentofu
# Check specific dependency
dependency-installer check --dependency opentofu

# List all tools with status
# List all dependencies with status
dependency-installer list

# Enable verbose logging
# Control log level (off, error, warn, info, debug, trace)
dependency-installer check --log-level debug
dependency-installer check --log-level off # Disable all logging

# Enable verbose logging (equivalent to --log-level debug)
dependency-installer check --verbose

# Get help
Expand All @@ -49,42 +67,49 @@ dependency-installer check --help
- **2**: Invalid arguments
- **3**: Internal error

#### Examples
#### Output Format

The tool uses structured logging (via `tracing`) instead of plain text output:

```bash
# Check all dependencies
# Check all dependencies (default log level shows INFO and above)
$ dependency-installer check
Checking dependencies...

✓ cargo-machete: installed
✗ OpenTofu: not installed
✗ Ansible: not installed
✓ LXD: installed

Missing 2 out of 4 required dependencies

# Check specific tool (with aliases)
$ dependency-installer check --tool tofu
✗ OpenTofu: not installed

# List all tools
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

# List all dependencies
$ dependency-installer list
Available tools:

- cargo-machete (installed)
- OpenTofu (not installed)
- Ansible (not installed)
- LXD (installed)
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"
...
```

#### Tool Aliases
#### Dependency Names

The CLI accepts multiple aliases for tools:
The CLI accepts the following dependency names:

- `cargo-machete` or `machete`
- `opentofu` or `tofu`
- `ansible`
- `lxd`
- `cargo-machete` - Rust dependency analyzer
- `opentofu` - Infrastructure provisioning tool
- `ansible` - Configuration management tool
- `lxd` - Lightweight VM manager

### Library Usage

Expand All @@ -94,33 +119,44 @@ The CLI accepts multiple aliases for tools:
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 {
println!("{}: {}", result.tool, if result.installed { "✓" } else { "✗" });
let detector = manager.get_detector(result.dependency);
tracing::info!(
dependency = detector.name(),
installed = result.installed,
"Dependency status"
);
}

Ok(())
}
```

#### Using Individual Detectors

```rust
use torrust_dependency_installer::{ToolDetector, OpenTofuDetector};
use torrust_dependency_installer::{DependencyDetector, Dependency, DependencyManager};

fn main() -> Result<(), Box<dyn std::error::Error>> {
let detector = OpenTofuDetector;

tracing_subscriber::fmt::init();

let manager = DependencyManager::new();
let detector = manager.get_detector(Dependency::OpenTofu);

if detector.is_installed()? {
println!("{} is installed", detector.name());
tracing::info!(dependency = detector.name(), "Dependency is installed");
} else {
println!("{} is not installed", detector.name());
tracing::warn!(dependency = detector.name(), "Dependency is not installed");
}

Ok(())
}
```
Expand Down
60 changes: 60 additions & 0 deletions packages/dependency-installer/docker/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Docker Testing Infrastructure

This directory contains Docker configurations for testing the dependency-installer CLI.

## Images

### ubuntu-24.04.Dockerfile

Base Ubuntu 24.04 image for testing the CLI binary in a clean environment.

**Purpose**: Verify that the `dependency-installer check` command correctly detects missing tools.

**Usage in tests**:

```rust
let image = GenericImage::new("ubuntu", "24.04")
.with_wait_for(WaitFor::message_on_stdout("Ready"));
```

## Testing Strategy

1. Build the binary: `cargo build --bin dependency-installer`
2. Copy binary into container using testcontainers
3. Run `check` command in container
4. Verify it correctly reports missing tools
5. (Phase 4) Install tools and verify installation

## Building Images

```bash
cd packages/dependency-installer
docker build -f docker/ubuntu-24.04.Dockerfile -t dependency-installer-test:ubuntu-24.04 .
```

## Running Tests

```bash
# Run all Docker-based integration tests
cd packages/dependency-installer
cargo test --test docker_check_command

# Run a specific test
cargo test --test docker_check_command test_check_all_reports_missing_dependencies
```

## Container Architecture

The tests use testcontainers to:

- Automatically start and stop Docker containers
- Copy the compiled binary into containers
- Execute commands and capture output
- Verify exit codes and output messages

This ensures tests run in isolated, reproducible environments.

## Related

- `tests/docker_check_command.rs` - Integration tests using this infrastructure
- `tests/containers/` - Container helper utilities
30 changes: 30 additions & 0 deletions packages/dependency-installer/docker/ubuntu-24.04.Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Dockerfile for Testing the Dependency Installer CLI
#
# This minimal Ubuntu 24.04 image is used for integration testing of the
# dependency-installer CLI binary. It intentionally does NOT include the
# tools we need to detect (cargo-machete, OpenTofu, Ansible, LXD) so we
# can verify that the CLI correctly identifies missing dependencies.

FROM ubuntu:24.04

# Metadata
LABEL description="Ubuntu 24.04 testing environment for dependency-installer CLI"
LABEL maintainer="Torrust Development Team"
LABEL version="1.0.0"
LABEL purpose="dependency-installer-integration-testing"

# Install minimal dependencies needed for running the binary
# Note: We intentionally do NOT install the tools we're testing for
RUN apt-get update && \
apt-get install -y \
ca-certificates \
&& rm -rf /var/lib/apt/lists/*

# Set working directory
WORKDIR /app

# The binary will be copied by testcontainers at runtime
# No need to copy it here - testcontainers handles that dynamically

# Default command - keeps container running for test execution
CMD ["/bin/bash", "-c", "while true; do sleep 1000; done"]
8 changes: 5 additions & 3 deletions packages/dependency-installer/examples/check_dependencies.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
use torrust_dependency_installer::{init_tracing, DependencyManager};

fn main() {
// Initialize tracing for structured logging
init_tracing();
// Initialize tracing for structured logging with INFO level
init_tracing(Some(tracing::Level::INFO));

println!("Checking development dependencies...\n");

Expand All @@ -23,14 +23,16 @@ fn main() {
println!("{}", "=".repeat(40));

for result in &results {
let detector = manager.get_detector(result.dependency);
let name = detector.name();
let status = if result.installed { "✓" } else { "✗" };
let status_text = if result.installed {
"Installed"
} else {
"Not Installed"
};

println!("{} {:20} {}", status, result.tool, status_text);
println!("{status} {name:20} {status_text}");
}

println!("\n{} dependencies checked", results.len());
Expand Down
Loading