Skip to content

Update EnvironmentCreationConfig DTO #208

@josecelano

Description

@josecelano

Task: Update EnvironmentCreationConfig

Epic: #205 - Add Hetzner Provider Support
Phase: 1 - Make LXD Explicit
Dependencies: #206 (Add Provider enum and ProviderConfig types)

Overview

Update the EnvironmentCreationConfig application layer config type to include provider configuration. This enables users to specify their provider in the environment JSON file.

Current State

// src/application/command_handlers/create/config/environment_creation_config.rs (current)
pub struct EnvironmentCreationConfig {
    pub environment: EnvironmentSection,
    pub ssh_credentials: SshCredentialsConfig,
    // ... no provider field
}

Current JSON format:

{
  "environment": {
    "instance_name": "torrust-tracker",
    "profile_name": "torrust-tracker"
  },
  "ssh_credentials": { ... }
}

Target State

// src/application/command_handlers/create/config/environment_creation_config.rs (after)
use super::provider_section::ProviderSection;

pub struct EnvironmentCreationConfig {
    pub environment: EnvironmentSection,
    pub provider: ProviderSection,      // NEW: Provider configuration
    pub ssh_credentials: SshCredentialsConfig,
}

impl EnvironmentCreationConfig {
    pub fn to_environment_params(&self) -> Result<EnvironmentParams, ConfigError> {
        // Convert provider section to domain type
        let provider_config = self.provider.to_provider_config()?;

        // ... rest of conversion
    }
}

Target JSON format:

{
  "environment": {
    "instance_name": "torrust-tracker"
  },
  "provider": {
    "provider": "lxd",
    "profile_name": "torrust-tracker"
  },
  "ssh_credentials": { ... }
}

Implementation Steps

  1. Add import for ProviderSection from config module
  2. Add provider field to EnvironmentCreationConfig
  3. Update to_environment_params() to convert provider config
  4. Remove profile_name from EnvironmentSection (moved to provider)
  5. Update error types to handle provider config errors
  6. Update JSON examples in documentation

Code Changes Required

Files to Modify

  1. src/application/command_handlers/create/config/environment_creation_config.rs
  2. src/application/command_handlers/create/config/environment_section.rs (remove profile_name)
  3. Error handling module for new error variants
  4. Documentation examples

Updated to_environment_params() Method

impl EnvironmentCreationConfig {
    /// Converts the config to domain parameters.
    ///
    /// This method:
    /// 1. Validates all string fields and converts to domain types
    /// 2. Converts `ProviderSection` to domain `ProviderConfig` with validation
    /// 3. Returns validated domain objects ready for `Environment::new()`
    ///
    /// # Errors
    ///
    /// Returns an error if any field validation fails.
    pub fn to_environment_params(&self) -> Result<EnvironmentParams, ConfigError> {
        let instance_name = InstanceName::new(&self.environment.instance_name)
            .map_err(ConfigError::InvalidInstanceName)?;

        let provider_config = self.provider.to_provider_config()
            .map_err(ConfigError::InvalidProviderConfig)?;

        // ... rest of conversion
    }
}

JSON Schema Changes

Before (Current)

{
  "environment": {
    "instance_name": "string",
    "profile_name": "string"
  },
  "ssh_credentials": { ... }
}

After (Target)

{
  "environment": {
    "instance_name": "string"
  },
  "provider": {
    "provider": "lxd | hetzner",
    // LXD-specific:
    "profile_name": "string",
    // Hetzner-specific:
    "api_token": "string",
    "server_type": "string",
    "location": "string",
    "image": "string"
  },
  "ssh_credentials": { ... }
}

Error Handling

Add new error variant for provider configuration:

#[derive(Debug, thiserror::Error)]
pub enum ConfigError {
    // ... existing variants

    #[error("Invalid provider configuration: {0}")]
    InvalidProviderConfig(#[from] ProviderSectionError),
}

Testing Requirements

Unit Tests

  • EnvironmentCreationConfig deserializes with LXD provider
  • EnvironmentCreationConfig deserializes with Hetzner provider
  • to_environment_params() converts provider config correctly
  • Error when provider section is missing
  • Error when provider type is invalid
  • Error when LXD profile name is invalid

Test Examples

#[test]
fn deserializes_lxd_provider() {
    let json = r#"{
        "environment": { "instance_name": "test" },
        "provider": { "provider": "lxd", "profile_name": "default" },
        "ssh_credentials": { ... }
    }"#;

    let config: EnvironmentCreationConfig = serde_json::from_str(json).unwrap();
    assert!(matches!(config.provider, ProviderSection::Lxd(_)));
}

#[test]
fn to_environment_params_validates_provider() {
    let config = EnvironmentCreationConfig {
        provider: ProviderSection::Lxd(LxdProviderSection {
            profile_name: "invalid name!".to_string(), // Invalid
        }),
        // ...
    };

    let result = config.to_environment_params();
    assert!(result.is_err());
}

Acceptance Criteria

  • EnvironmentCreationConfig has provider: ProviderSection field
  • profile_name removed from EnvironmentSection (moved to provider)
  • to_environment_params() converts ProviderSection to ProviderConfig
  • Clear error messages for invalid provider configuration
  • JSON examples updated in documentation
  • All tests pass
  • No compiler warnings

Migration Notes

This is a breaking change to the JSON configuration format. Existing environment JSON files will need to be updated:

Before:

{
  "environment": {
    "instance_name": "torrust-tracker",
    "profile_name": "torrust-tracker"
  }
}

After:

{
  "environment": {
    "instance_name": "torrust-tracker"
  },
  "provider": {
    "provider": "lxd",
    "profile_name": "torrust-tracker"
  }
}

Related

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions