Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Oct 25, 2025

Implementation Plan: CLI Presentation Layer for Create Command

Based on the issue requirements and codebase exploration, here's the minimal-change implementation plan:

Understanding

  • Explored existing codebase structure
  • Reviewed application layer (CreateCommandHandler)
  • Studied existing destroy command pattern
  • Confirmed tests pass (918 passed initially, now 953 passed)

Implementation Tasks

  • Add Figment dependency to Cargo.toml
  • Create presentation layer module structure in src/presentation/commands/create/
    • mod.rs - module exports
    • subcommand.rs - main command handler
    • config_loader.rs - Figment integration
    • errors.rs - presentation error types with .help()
    • tests/ directory with integration tests
  • Update CLI Commands enum to include Create subcommand
  • Add --working-dir flag to GlobalArgs (main CLI level)
  • Integrate create command into presentation layer execution
  • Add error handling in CommandError enum
  • Write comprehensive tests (29 create-specific tests, all passing)
  • Run linter and fix any issues (all linters passing)
  • Manual verification with test config files ✅
  • Fix doctest failures ✅

Key Design Decisions

  • Follow existing destroy command pattern for consistency
  • Use Figment for JSON configuration file parsing (stays in presentation layer)
  • Implement tiered help system with .help() methods on all errors
  • Support --working-dir at main CLI level for production use
  • Use RepositoryFactory with working directory override

Test Results

All 953 tests passing, including:

  • 29 create command specific tests
  • CLI argument parsing tests
  • Configuration loading tests
  • Error handling tests
  • Integration tests with temporary directories
  • All 197 doctests passing

Linter Results

All linters passing:

  • ✅ Markdown
  • ✅ YAML
  • ✅ TOML
  • ✅ CSpell
  • ✅ Clippy
  • ✅ Rustfmt
  • ✅ ShellCheck

Manual Verification Results ✅

Successfully tested:

  1. ✅ Help command: torrust-tracker-deployer create --help
  2. ✅ Valid configuration: Environment created successfully with all metadata
  3. ✅ Non-existent file error: Clear error with troubleshooting steps
  4. ✅ Invalid JSON error: Detailed parsing error with examples
  5. ✅ Invalid environment name: Validation error with naming rules
  6. ✅ Duplicate environment: Clear error with resolution options
  7. ✅ Working directory flag: --working-dir correctly changes storage location

All error messages include actionable troubleshooting guidance with .help() methods as specified.

Original prompt

This section details on the original issue you should resolve

<issue_title>[Subissue 3/7] CLI Presentation Layer</issue_title>
<issue_description>## Overview

Implement the CLI presentation layer for the create command, handling Figment integration for configuration file parsing, argument processing, and user interaction.

Parent EPIC: #34 - Implement Create Environment Command
Depends On: #36 (Application Layer CreateCommand)
Related: Full Specification

Key Points

  • Add --working-dir flag to main CLI for production use (not just tests)
  • Figment stays in presentation layer as delivery mechanism
  • All errors use tiered help system with .help() methods

Goals

  • Create create subcommand in presentation layer

    • Figment integration for configuration file parsing (JSON format)
    • Argument parsing (--env-file, --working-dir)
    • Configuration file loading using Figment
    • Conversion from raw file data to clean domain objects
    • Call application layer CreateCommand with domain objects
    • User feedback and progress indication
    • Error message presentation with helpful context
  • Add explicit presentation error enums

    • CreateSubcommandError for CLI-specific errors
    • Error conversion from application layer to user-friendly messages
    • All errors implement .help() methods
  • Command help documentation

    • Usage examples
    • Argument descriptions
    • Error guidance
  • Tests

    • Integration tests for CLI interface with temporary directories
    • Unit tests for argument parsing and Figment integration
    • Error presentation tests

Module Structure

src/presentation/console/subcommands/create/
├── mod.rs                    # Module exports
├── subcommand.rs             # CreateSubcommand implementation
├── args.rs                   # CLI argument definitions
├── config_loader.rs          # Figment integration
├── errors.rs                 # Presentation error types with .help()
└── tests/
    ├── mod.rs
    ├── integration.rs        # CLI integration tests
    └── fixtures.rs           # Test fixtures

CLI Arguments

#[derive(Debug, Args)]
pub struct CreateArgs {
    #[command(subcommand)]
    pub action: CreateAction,
}

#[derive(Debug, Subcommand)]
pub enum CreateAction {
    Environment {
        /// Path to the environment configuration file
        #[arg(long, short = 'f', value_name = "FILE")]
        env_file: PathBuf,
    },
    // Future: Template generation
}

Configuration Loader (Figment)

pub struct ConfigLoader;

impl ConfigLoader {
    pub fn load_from_file(
        &self, 
        config_path: &Path
    ) -> Result<EnvironmentCreationConfig, CreateSubcommandError> {
        // Verify file exists
        if !config_path.exists() {
            return Err(CreateSubcommandError::ConfigFileNotFound {
                path: config_path.to_path_buf(),
            });
        }

        // Load with Figment
        let config = Figment::new()
            .merge(Serialized::defaults(EnvironmentCreationConfig::default()))
            .merge(Json::file(config_path))
            .extract()
            .map_err(|source| CreateSubcommandError::ConfigParsingFailed {
                path: config_path.to_path_buf(),
                format: ConfigFormat::Json,
                source: Box::new(source),
            })?;

        // Validate using domain rules
        config.validate()
            .map_err(CreateSubcommandError::ConfigValidationFailed)?;

        Ok(config)
    }
}

Error Types with .help()

#[derive(Debug, Error)]
pub enum CreateSubcommandError {
    #[error("Configuration file not found: {path}")]
    ConfigFileNotFound { path: PathBuf },

    #[error("Failed to parse configuration file: {path}")]
    ConfigParsingFailed {
        path: PathBuf,
        format: ConfigFormat,
        #[source]
        source: Box<dyn std::error::Error + Send + Sync>,
    },

    #[error("Configuration validation failed")]
    ConfigValidationFailed(#[source] ConfigValidationError),

    #[error("Command execution failed")]
    CommandFailed(#[source] CreateCommandError),
}

impl CreateSubcommandError {
    pub fn help(&self) -> &'static str {
        // Detailed troubleshooting guidance
    }
}

Usage Examples

# Create environment from configuration file
torrust-tracker-deployer create environment --env-file ./config/environment.json

# With custom working directory
torrust-tracker-deployer --working-dir /path/to/workspace create environment --env-file ./config/env.json

# Show help
torrust-tracker-deployer create --help
torrust-tracker-deployer create environment --help

Acceptance Criteria

  • `cre...

💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

Copilot AI self-assigned this Oct 25, 2025
@josecelano josecelano marked this pull request as ready for review October 25, 2025 17:58
Copilot AI changed the title [WIP] Implement CLI presentation layer for create command feat: add CLI presentation layer for create command with Figment integration Oct 25, 2025
Copilot AI requested a review from josecelano October 25, 2025 18:36
Copilot finished work on behalf of josecelano October 25, 2025 18:36
@josecelano
Copy link
Member

@copilot fix "testing" workflow failures:

failures:

---- src/presentation/cli/args.rs - presentation::cli::args::GlobalArgs::logging_config (line 88) stdout ----
error[E0063]: missing field `working_dir` in initializer of `GlobalArgs`
 --> src/presentation/cli/args.rs:94:12
  |
9 | let args = GlobalArgs {
  |            ^^^^^^^^^^ missing `working_dir`

error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0063`.
Couldn't compile the test.
---- src/presentation/commands/mod.rs - presentation::commands::execute (line 40) stdout ----
error[E0061]: this function takes 2 arguments but 1 argument was supplied
  --> src/presentation/commands/mod.rs:47:18
   |
10 |     let result = commands::execute(command);
   |                  ^^^^^^^^^^^^^^^^^--------- argument #2 of type `&Path` is missing
   |
note: function defined here
  --> /home/runner/work/torrust-tracker-deployer/torrust-tracker-deployer/src/presentation/commands/mod.rs:53:8
   |
53 | pub fn execute(command: Commands, working_dir: &std::path::Path) -> Result<(), CommandError> {
   |        ^^^^^^^
help: provide the argument
   |
10 |     let result = commands::execute(command, /* &Path */);
   |                                           +++++++++++++

error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0061`.
Couldn't compile the test.

failures:
    src/presentation/cli/args.rs - presentation::cli::args::GlobalArgs::logging_config (line 88)
    src/presentation/commands/mod.rs - presentation::commands::execute (line 40)

test result: FAILED. 195 passed; 2 failed; 1 ignored; 0 measured; 0 filtered out; finished in 37.57s

error: doctest failed, to rerun pass `-p torrust-tracker-deployer --doc`
Error: Process completed with exit code 101.

Workflow execution: https://github.com/torrust/torrust-tracker-deployer/actions/runs/18806928179/job/53669010936?pr=45

- Add working_dir field to GlobalArgs doctest example
- Update execute() doctest to pass working_dir parameter

Co-authored-by: josecelano <[email protected]>
Copy link
Contributor Author

Copilot AI commented Oct 25, 2025

@copilot fix "testing" workflow failures:

...

Fixed in commit f9a7652. The doctests were missing the new working_dir field and parameter that were added as part of the feature implementation. Both doctests now compile and all 197 doctests pass.

Copilot finished work on behalf of josecelano October 25, 2025 23:21
Copilot AI requested a review from josecelano October 25, 2025 23:21
Copy link
Member

@josecelano josecelano left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK f9a7652

@josecelano josecelano merged commit 2307191 into main Oct 26, 2025
43 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Subissue 3/7] CLI Presentation Layer

2 participants