Skip to content

Commit abb2991

Browse files
committed
Merge #132: feat: Add Docker test infrastructure for dependency-installer CLI
0b0beda test: [#116] rename tests to follow it_should pattern and reveal intentions (Jose Celano) 194ce60 refactor: [#116] apply module organization conventions to dependency-installer package (Jose Celano) 3572826 refactor: [#116] improve structured logging with consistent status field (Jose Celano) 7d9fefb feat: [#116] add --log-level CLI option to control logging output (Jose Celano) 6c62e5f refactor: [#116] simplify dependency-installer output to use structured logging only (Jose Celano) 1a7c639 refactor: [#116] normalize 'tool' to 'dependency' terminology and use Dependency enum in error types (Jose Celano) 2268d4b refactor: [#116] use Dependency enum for type-safe dependency references (Jose Celano) 7bc0b8f refactor: [#116] eliminate map_err calls using From trait implementations (Jose Celano) 36f9627 refactor: [#116] replace Box<dyn Error> with concrete error enums in dependency-installer (Jose Celano) 7e461ed refactor: [#116] extract handlers module with check and list submodules (Jose Celano) 7da8994 refactor: [#116] extract logging into dedicated module (Jose Celano) 8048ea8 refactor: [#116] introduce ExitCode type alias (Jose Celano) 98b1b9c refactor: [#116] introduce CommandOutput type for container execution (Jose Celano) 41690ea refactor: [#116] improve container test utilities structure (Jose Celano) a3b70c4 fix: [#116] address clippy linting errors (copilot-swe-agent[bot]) 7432591 refactor: [#116] address code review feedback (copilot-swe-agent[bot]) 2040c38 style: [#116] fix rustfmt formatting issues (copilot-swe-agent[bot]) b5af2fc feat: [#116] add Docker test infrastructure for CLI (copilot-swe-agent[bot]) c302577 Initial plan (copilot-swe-agent[bot]) Pull request description: Create Docker Test Infrastructure for CLI Binary Testing This PR implements Docker-based testing infrastructure to verify the CLI binary works correctly in a clean Ubuntu 24.04 environment. **Implementation Plan:** - [x] Create Dockerfile for Ubuntu 24.04 testing environment - [x] Create Docker README documentation - [x] Add testcontainers dependency to Cargo.toml - [x] Create container helper modules (ubuntu.rs, helpers.rs, mod.rs) - [x] Create integration test file (docker_check_command.rs) - [x] Implement test for checking missing dependencies - [x] Implement test for checking specific tools - [x] Implement test for list command - [x] Implement test for verbose output - [x] Manual verification of all tests (all 4 tests passing) - [x] Run pre-commit checks and fix any issues - [x] Address code review feedback - [x] Fix all linting errors **Linting Fixes (commit hash pending):** - Fixed `uninlined_format_args` warnings - updated all format strings to use inline syntax - Fixed `doc_markdown` warnings - added backticks to `code()`, `CARGO_MANIFEST_DIR`, etc. - Fixed `unused_self` warning - added `#[allow(clippy::unused_self)]` to fluent builder method - Fixed `unnecessary_debug_formatting` - changed `{:?}` to `Display` formatting with `.display()` **All Linters Passing:** - ✅ Markdown linting - ✅ YAML linting - ✅ TOML linting - ✅ Spell checking (cspell) - ✅ Clippy (Rust linting) - ✅ Rustfmt (code formatting) - ✅ ShellCheck **Test Results:** ``` running 4 tests test test_check_all_reports_missing_dependencies ... ok test test_check_specific_tool ... ok test test_list_command ... ok test test_verbose_output ... ok test result: ok. 4 passed; 0 failed; 0 ignored ``` <!-- START COPILOT CODING AGENT SUFFIX --> <details> <summary>Original prompt</summary> > > ---- > > *This section details on the original issue you should resolve* > > <issue_title>Create Docker Test Infrastructure</issue_title> > <issue_description>**Parent Issue**: #113 - Create Dependency Installation Package for E2E Tests > **Depends On**: #115 - Create CLI Binary with Check Command (Issue 1-1-2) > **Epic**: #112 - Refactor and Improve E2E Test Execution > > ## Overview > > Create Docker-based testing infrastructure to verify the CLI binary works correctly in a clean Ubuntu environment. This ensures the binary can detect missing dependencies and provides confidence before implementing installation logic. > > This is **Phase 3 of 4** for building the dependency installation package. > > ## Objectives > > - Create Dockerfile based on ubuntu:24.04 > - Set up container helpers for file operations > - Write Docker-based tests for the CLI binary > - Test detection of missing and installed tools > - Verify exit codes and error messages > > ## Key Components > > **Dockerfile**: > - Base: ubuntu:24.04 > - Installs: build-essential, curl > - Does NOT install: cargo-machete, OpenTofu, Ansible, LXD (tests detection of missing deps) > > **Container Helpers**: > - Copy existing helpers from `src/testing/e2e/containers/` > - `copy_file_to_container()` - Copy binary to container > - Container startup and cleanup utilities > > **Tests**: > - Test binary detects missing dependencies correctly > - Test binary reports installed dependencies correctly > - Verify exit codes (0 for all present, 1 for missing deps) > > ## Acceptance Criteria > > - Pre-commit checks pass > - Dockerfile builds successfully > - Docker tests run and pass > - CLI binary correctly detects missing tools > - Exit codes are correct in all scenarios > - Tests are documented and maintainable > > ## Time Estimate > > 2-3 hours > > ## Related Documentation > > - Full specification: [docs/issues/116-1-1-3-create-docker-test-infrastructure.md](https://github.com/torrust/torrust-tracker-deployer/blob/main/docs/issues/116-1-1-3-create-docker-test-infrastructure.md) > - Issue 1-1-2: CLI binary being tested</issue_description> > > ## Comments on the Issue (you are @copilot in this section) > > <comments> > </comments> > </details> - Fixes #116 <!-- START COPILOT CODING AGENT TIPS --> --- 💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more [Copilot coding agent tips](https://gh.io/copilot-coding-agent-tips) in the docs. ACKs for top commit: josecelano: ACK 0b0beda Tree-SHA512: 74d1a3bf8dda5271f3c84b2f4792dcef886977efeb8509d11c2f84c6a8c4477034df8cee4160c31f289d444058ce3eb686c6df84edfb8f0dbb37f3861549873e
2 parents 9e08c7b + 0b0beda commit abb2991

29 files changed

+1531
-338
lines changed

packages/dependency-installer/Cargo.toml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,11 @@ clap = { version = "4.0", features = [ "derive" ] }
1818
thiserror = "1.0"
1919
tracing = "0.1"
2020
tracing-subscriber = { version = "0.3", features = [ "env-filter" ] }
21+
22+
[dev-dependencies]
23+
testcontainers = "0.25"
24+
tokio = { version = "1.0", features = [ "full" ] }
25+
26+
[[test]]
27+
name = "docker_check_command"
28+
path = "tests/docker_check_command.rs"

packages/dependency-installer/README.md

Lines changed: 83 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,28 @@
22

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

5+
## Design Philosophy
6+
7+
**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.
8+
9+
This design choice offers several benefits:
10+
11+
- **Automation-friendly**: Structured logs are easy to parse and filter programmatically
12+
- **Consistent**: Same output format as the rest of the Torrust ecosystem
13+
- **Simple**: The tool is straightforward enough that logging output is sufficient even for manual use
14+
- **Observable**: Rich contextual information through structured fields
15+
16+
For manual usage, you can control log verbosity with the `--verbose` flag or `RUST_LOG` environment variable.
17+
518
## Features
619

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

12-
## Required Tools
26+
## Supported Dependencies
1327

1428
This package can detect the following development dependencies:
1529

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

2741
```bash
28-
# Check all dependencies
42+
# Check all dependencies (default: info log level)
2943
dependency-installer check
3044

31-
# Check specific tool
32-
dependency-installer check --tool opentofu
45+
# Check specific dependency
46+
dependency-installer check --dependency opentofu
3347

34-
# List all tools with status
48+
# List all dependencies with status
3549
dependency-installer list
3650

37-
# Enable verbose logging
51+
# Control log level (off, error, warn, info, debug, trace)
52+
dependency-installer check --log-level debug
53+
dependency-installer check --log-level off # Disable all logging
54+
55+
# Enable verbose logging (equivalent to --log-level debug)
3856
dependency-installer check --verbose
3957

4058
# Get help
@@ -49,42 +67,49 @@ dependency-installer check --help
4967
- **2**: Invalid arguments
5068
- **3**: Internal error
5169

52-
#### Examples
70+
#### Output Format
71+
72+
The tool uses structured logging (via `tracing`) instead of plain text output:
5373

5474
```bash
55-
# Check all dependencies
75+
# Check all dependencies (default log level shows INFO and above)
5676
$ dependency-installer check
57-
Checking dependencies...
58-
59-
✓ cargo-machete: installed
60-
✗ OpenTofu: not installed
61-
✗ Ansible: not installed
62-
✓ LXD: installed
63-
64-
Missing 2 out of 4 required dependencies
65-
66-
# Check specific tool (with aliases)
67-
$ dependency-installer check --tool tofu
68-
✗ OpenTofu: not installed
69-
70-
# List all tools
77+
2025-11-04T17:33:20.959847Z INFO torrust_dependency_installer::handlers::check: Checking all dependencies
78+
2025-11-04T17:33:20.960126Z INFO torrust_dependency_installer::handlers::check: Dependency check result dependency="cargo-machete" status="installed"
79+
2025-11-04T17:33:20.960131Z INFO torrust_dependency_installer::handlers::check: Dependency check result dependency="OpenTofu" status="not installed"
80+
2025-11-04T17:33:20.960136Z INFO torrust_dependency_installer::handlers::check: Dependency check result dependency="Ansible" status="not installed"
81+
2025-11-04T17:33:20.960139Z INFO torrust_dependency_installer::handlers::check: Dependency check result dependency="LXD" status="installed"
82+
2025-11-04T17:33:20.960144Z INFO torrust_dependency_installer::handlers::check: Missing dependencies missing_count=2 total_count=4
83+
Error: Check command failed: Failed to check all dependencies: Missing 2 out of 4 required dependencies
84+
85+
# Check specific dependency
86+
$ dependency-installer check --dependency opentofu
87+
2025-11-04T17:33:20.959855Z INFO torrust_dependency_installer::handlers::check: Checking specific dependency dependency=opentofu
88+
2025-11-04T17:33:20.960473Z INFO torrust_dependency_installer::detector::opentofu: OpenTofu is not installed dependency="opentofu"
89+
2025-11-04T17:33:20.960482Z INFO torrust_dependency_installer::handlers::check: Dependency is not installed dependency="OpenTofu" status="not installed"
90+
Error: Check command failed: Failed to check specific dependency: opentofu: not installed
91+
92+
# List all dependencies
7193
$ dependency-installer list
72-
Available tools:
73-
74-
- cargo-machete (installed)
75-
- OpenTofu (not installed)
76-
- Ansible (not installed)
77-
- LXD (installed)
94+
2025-11-04T17:33:20.960482Z INFO torrust_dependency_installer::handlers::list: Available dependency dependency="cargo-machete" status="installed"
95+
2025-11-04T17:33:20.960494Z INFO torrust_dependency_installer::handlers::list: Available dependency dependency="OpenTofu" status="not installed"
96+
2025-11-04T17:33:20.960962Z INFO torrust_dependency_installer::handlers::list: Available dependency dependency="Ansible" status="not installed"
97+
2025-11-04T17:33:20.961521Z INFO torrust_dependency_installer::handlers::list: Available dependency dependency="LXD" status="installed"
98+
99+
# Enable verbose logging (includes DEBUG level)
100+
$ dependency-installer check --verbose
101+
2025-11-04T17:33:20.959872Z DEBUG torrust_dependency_installer::detector::cargo_machete: Checking if cargo-machete is installed dependency="cargo-machete"
102+
...
78103
```
79104
80-
#### Tool Aliases
105+
#### Dependency Names
81106
82-
The CLI accepts multiple aliases for tools:
107+
The CLI accepts the following dependency names:
83108
84-
- `cargo-machete` or `machete`
85-
- `opentofu` or `tofu`
86-
- `ansible`
87-
- `lxd`
109+
- `cargo-machete` - Rust dependency analyzer
110+
- `opentofu` - Infrastructure provisioning tool
111+
- `ansible` - Configuration management tool
112+
- `lxd` - Lightweight VM manager
88113
89114
### Library Usage
90115
@@ -94,33 +119,44 @@ The CLI accepts multiple aliases for tools:
94119
use torrust_dependency_installer::DependencyManager;
95120

96121
fn main() -> Result<(), Box<dyn std::error::Error>> {
122+
// Initialize tracing for structured logging
123+
tracing_subscriber::fmt::init();
124+
97125
let manager = DependencyManager::new();
98-
126+
99127
// Check all dependencies
100128
let results = manager.check_all()?;
101-
129+
102130
for result in results {
103-
println!("{}: {}", result.tool, if result.installed { "" } else { "" });
131+
let detector = manager.get_detector(result.dependency);
132+
tracing::info!(
133+
dependency = detector.name(),
134+
installed = result.installed,
135+
"Dependency status"
136+
);
104137
}
105-
138+
106139
Ok(())
107140
}
108141
```
109142
110143
#### Using Individual Detectors
111144
112145
```rust
113-
use torrust_dependency_installer::{ToolDetector, OpenTofuDetector};
146+
use torrust_dependency_installer::{DependencyDetector, Dependency, DependencyManager};
114147

115148
fn main() -> Result<(), Box<dyn std::error::Error>> {
116-
let detector = OpenTofuDetector;
117-
149+
tracing_subscriber::fmt::init();
150+
151+
let manager = DependencyManager::new();
152+
let detector = manager.get_detector(Dependency::OpenTofu);
153+
118154
if detector.is_installed()? {
119-
println!("{} is installed", detector.name());
155+
tracing::info!(dependency = detector.name(), "Dependency is installed");
120156
} else {
121-
println!("{} is not installed", detector.name());
157+
tracing::warn!(dependency = detector.name(), "Dependency is not installed");
122158
}
123-
159+
124160
Ok(())
125161
}
126162
```
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# Docker Testing Infrastructure
2+
3+
This directory contains Docker configurations for testing the dependency-installer CLI.
4+
5+
## Images
6+
7+
### ubuntu-24.04.Dockerfile
8+
9+
Base Ubuntu 24.04 image for testing the CLI binary in a clean environment.
10+
11+
**Purpose**: Verify that the `dependency-installer check` command correctly detects missing tools.
12+
13+
**Usage in tests**:
14+
15+
```rust
16+
let image = GenericImage::new("ubuntu", "24.04")
17+
.with_wait_for(WaitFor::message_on_stdout("Ready"));
18+
```
19+
20+
## Testing Strategy
21+
22+
1. Build the binary: `cargo build --bin dependency-installer`
23+
2. Copy binary into container using testcontainers
24+
3. Run `check` command in container
25+
4. Verify it correctly reports missing tools
26+
5. (Phase 4) Install tools and verify installation
27+
28+
## Building Images
29+
30+
```bash
31+
cd packages/dependency-installer
32+
docker build -f docker/ubuntu-24.04.Dockerfile -t dependency-installer-test:ubuntu-24.04 .
33+
```
34+
35+
## Running Tests
36+
37+
```bash
38+
# Run all Docker-based integration tests
39+
cd packages/dependency-installer
40+
cargo test --test docker_check_command
41+
42+
# Run a specific test
43+
cargo test --test docker_check_command test_check_all_reports_missing_dependencies
44+
```
45+
46+
## Container Architecture
47+
48+
The tests use testcontainers to:
49+
50+
- Automatically start and stop Docker containers
51+
- Copy the compiled binary into containers
52+
- Execute commands and capture output
53+
- Verify exit codes and output messages
54+
55+
This ensures tests run in isolated, reproducible environments.
56+
57+
## Related
58+
59+
- `tests/docker_check_command.rs` - Integration tests using this infrastructure
60+
- `tests/containers/` - Container helper utilities
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Dockerfile for Testing the Dependency Installer CLI
2+
#
3+
# This minimal Ubuntu 24.04 image is used for integration testing of the
4+
# dependency-installer CLI binary. It intentionally does NOT include the
5+
# tools we need to detect (cargo-machete, OpenTofu, Ansible, LXD) so we
6+
# can verify that the CLI correctly identifies missing dependencies.
7+
8+
FROM ubuntu:24.04
9+
10+
# Metadata
11+
LABEL description="Ubuntu 24.04 testing environment for dependency-installer CLI"
12+
LABEL maintainer="Torrust Development Team"
13+
LABEL version="1.0.0"
14+
LABEL purpose="dependency-installer-integration-testing"
15+
16+
# Install minimal dependencies needed for running the binary
17+
# Note: We intentionally do NOT install the tools we're testing for
18+
RUN apt-get update && \
19+
apt-get install -y \
20+
ca-certificates \
21+
&& rm -rf /var/lib/apt/lists/*
22+
23+
# Set working directory
24+
WORKDIR /app
25+
26+
# The binary will be copied by testcontainers at runtime
27+
# No need to copy it here - testcontainers handles that dynamically
28+
29+
# Default command - keeps container running for test execution
30+
CMD ["/bin/bash", "-c", "while true; do sleep 1000; done"]

packages/dependency-installer/examples/check_dependencies.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
use torrust_dependency_installer::{init_tracing, DependencyManager};
99

1010
fn main() {
11-
// Initialize tracing for structured logging
12-
init_tracing();
11+
// Initialize tracing for structured logging with INFO level
12+
init_tracing(Some(tracing::Level::INFO));
1313

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

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

2525
for result in &results {
26+
let detector = manager.get_detector(result.dependency);
27+
let name = detector.name();
2628
let status = if result.installed { "✓" } else { "✗" };
2729
let status_text = if result.installed {
2830
"Installed"
2931
} else {
3032
"Not Installed"
3133
};
3234

33-
println!("{} {:20} {}", status, result.tool, status_text);
35+
println!("{status} {name:20} {status_text}");
3436
}
3537

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

0 commit comments

Comments
 (0)