Skip to content

Commit aff118d

Browse files
committed
feat: add ProfileName domain type for LXD profile validation and environment isolation
- Create ProfileName domain type with LXD naming validation (1-63 chars, ASCII letters/numbers/dashes, no leading digit/dash, no trailing dash) - Add ProfileName field to Environment entity for environment-specific profile naming - Update Config struct to include ProfileName parameter - Integrate ProfileName throughout template rendering pipeline - Update VariablesContext and VariablesContextBuilder to use ProfileName type instead of String - Add profile_name template variable to OpenTofu variables and main.tf - Update E2E test cleanup to use environment-specific profile names - Add comprehensive unit tests for ProfileName validation - Ensure type safety and domain validation for LXD profile names - Enable proper environment isolation between E2E test runs This resolves LXD profile conflicts between different test environments by ensuring each environment uses its own uniquely named profile (e.g., torrust-profile-e2e-provision, torrust-profile-e2e-full) instead of sharing a single hardcoded profile name.
1 parent 5989a6a commit aff118d

File tree

15 files changed

+510
-16
lines changed

15 files changed

+510
-16
lines changed

src/application/commands/provision.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ use crate::application::steps::{
2323
RenderOpenTofuTemplatesStep, ValidateInfrastructureStep, WaitForCloudInitStep,
2424
WaitForSSHConnectivityStep,
2525
};
26+
#[allow(unused_imports)]
27+
use crate::domain::ProfileName;
2628
use crate::infrastructure::adapters::ansible::AnsibleClient;
2729
#[allow(unused_imports)]
2830
use crate::infrastructure::adapters::lxd::InstanceName;
@@ -206,6 +208,7 @@ mod tests {
206208
ssh_credentials.clone(),
207209
InstanceName::new("torrust-tracker-vm".to_string())
208210
.expect("Valid hardcoded instance name"), // TODO: Make this configurable in Phase 3
211+
ProfileName::new("default-profile".to_string()).expect("Valid hardcoded profile name"), // TODO: Make this configurable in Phase 3
209212
));
210213

211214
let ansible_renderer = Arc::new(AnsibleTemplateRenderer::new(

src/bin/e2e_provision_tests.rs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,9 @@ use tracing::{error, info};
4545
// Import E2E testing infrastructure
4646
use torrust_tracker_deploy::domain::{Environment, EnvironmentName};
4747
use torrust_tracker_deploy::e2e::context::{TestContext, TestContextType};
48-
use torrust_tracker_deploy::e2e::tasks::{
49-
preflight_cleanup::cleanup_lingering_resources,
50-
virtual_machine::{
51-
cleanup_infrastructure::cleanup_infrastructure,
52-
run_provision_command::run_provision_command,
53-
},
48+
use torrust_tracker_deploy::e2e::tasks::virtual_machine::{
49+
cleanup_infrastructure::cleanup_infrastructure, preflight_cleanup::cleanup_lingering_resources,
50+
run_provision_command::run_provision_command,
5451
};
5552
use torrust_tracker_deploy::logging::{self, LogFormat};
5653
use torrust_tracker_deploy::shared::Username;

src/bin/e2e_tests_full.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,11 @@ use tracing::{error, info};
5454
use torrust_tracker_deploy::domain::{Environment, EnvironmentName};
5555
use torrust_tracker_deploy::e2e::context::{TestContext, TestContextType};
5656
use torrust_tracker_deploy::e2e::tasks::{
57-
preflight_cleanup::cleanup_lingering_resources,
5857
run_configure_command::run_configure_command,
5958
run_test_command::run_test_command,
6059
virtual_machine::{
6160
cleanup_infrastructure::cleanup_infrastructure,
61+
preflight_cleanup::cleanup_lingering_resources,
6262
run_provision_command::run_provision_command,
6363
},
6464
};

src/config/mod.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
1717
use std::path::PathBuf;
1818

19-
pub use crate::domain::InstanceName;
19+
pub use crate::domain::{InstanceName, ProfileName};
2020
pub use crate::shared::ssh::{SshConnection, SshCredentials};
2121

2222
/// Configuration parameters for deployment environments.
@@ -48,6 +48,12 @@ pub struct Config {
4848
/// cannot end with dash.
4949
pub instance_name: InstanceName,
5050

51+
/// Name for the LXD profile to be created.
52+
///
53+
/// This name will be used for creating LXD profiles with environment-specific
54+
/// naming to ensure test isolation between different environments.
55+
pub profile_name: ProfileName,
56+
5157
/// Subdirectory name for Ansible-related files within the build directory.
5258
///
5359
/// Ansible playbooks, inventory files, and configuration templates
@@ -90,17 +96,20 @@ impl Config {
9096
/// # use std::net::{IpAddr, Ipv4Addr};
9197
/// # use std::path::PathBuf;
9298
/// # use torrust_tracker_deploy::config::{Config, SshCredentials, InstanceName};
99+
/// # use torrust_tracker_deploy::domain::ProfileName;
93100
/// # use torrust_tracker_deploy::shared::Username;
94101
/// let ssh_credentials = SshCredentials::new(
95102
/// PathBuf::from("/home/user/.ssh/deploy_key"),
96103
/// PathBuf::from("/home/user/.ssh/deploy_key.pub"),
97104
/// Username::new("ubuntu").unwrap(),
98105
/// );
99106
/// let instance_name = InstanceName::new("my-instance".to_string()).unwrap();
107+
/// let profile_name = ProfileName::new("my-profile".to_string()).unwrap();
100108
/// let config = Config::new(
101109
/// true, // keep environment for debugging
102110
/// ssh_credentials,
103111
/// instance_name,
112+
/// profile_name,
104113
/// "templates".to_string(),
105114
/// PathBuf::from("/path/to/project"),
106115
/// PathBuf::from("/path/to/project/build"),
@@ -111,6 +120,7 @@ impl Config {
111120
keep_env: bool,
112121
ssh_config: SshCredentials,
113122
instance_name: InstanceName,
123+
profile_name: ProfileName,
114124
templates_dir: String,
115125
project_root: PathBuf,
116126
build_dir: PathBuf,
@@ -119,6 +129,7 @@ impl Config {
119129
keep_env,
120130
ssh_credentials: ssh_config,
121131
instance_name,
132+
profile_name,
122133
ansible_subfolder: "ansible".to_string(),
123134
opentofu_subfolder: "tofu/lxd".to_string(),
124135
templates_dir,

src/container.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,8 @@ impl Services {
5656
template_manager.clone(),
5757
config.build_dir.clone(),
5858
config.ssh_credentials.clone(),
59-
config.instance_name.clone(), // Phase 3: Use instance_name from config instead of hardcoded value
59+
config.instance_name.clone(),
60+
config.profile_name.clone(),
6061
);
6162

6263
// Create configuration template renderer

src/domain/environment.rs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
//! ```
3838
3939
use crate::config::InstanceName;
40-
use crate::domain::EnvironmentName;
40+
use crate::domain::{EnvironmentName, ProfileName};
4141
use crate::shared::Username;
4242
use serde::{Deserialize, Serialize};
4343
use std::path::PathBuf;
@@ -75,6 +75,9 @@ pub struct Environment {
7575
/// The instance name for this environment (auto-generated)
7676
instance_name: InstanceName,
7777

78+
/// The profile name for this environment (auto-generated)
79+
profile_name: ProfileName,
80+
7881
/// SSH username for connecting to instances in this environment
7982
ssh_username: Username,
8083

@@ -147,13 +150,19 @@ impl Environment {
147150
let instance_name = InstanceName::new(instance_name_str)
148151
.expect("Generated instance name should always be valid");
149152

153+
// Generate profile name: torrust-profile-{env_name}
154+
let profile_name_str = format!("torrust-profile-{env_str}");
155+
let profile_name = ProfileName::new(profile_name_str)
156+
.expect("Generated profile name should always be valid");
157+
150158
// Generate environment-specific directories
151159
let data_dir = PathBuf::from("data").join(env_str);
152160
let build_dir = PathBuf::from("build").join(env_str);
153161

154162
Self {
155163
name,
156164
instance_name,
165+
profile_name,
157166
ssh_username,
158167
ssh_private_key_path,
159168
ssh_public_key_path,
@@ -174,6 +183,15 @@ impl Environment {
174183
&self.instance_name
175184
}
176185

186+
/// Returns the profile name for this environment
187+
///
188+
/// Returns the unique LXD profile name based on the environment name
189+
/// to ensure profile isolation between different test environments.
190+
#[must_use]
191+
pub fn profile_name(&self) -> &ProfileName {
192+
&self.profile_name
193+
}
194+
177195
/// Returns the SSH username for this environment
178196
#[must_use]
179197
pub fn ssh_username(&self) -> &Username {

src/domain/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,18 @@
88
//! - `environment` - Environment entity encapsulating all environment-specific configuration
99
//! - `environment_name` - Environment name validation and management
1010
//! - `instance_name` - LXD instance name validation and management
11+
//! - `profile_name` - LXD profile name validation and management
1112
//! - `template` - Core template domain models and business logic
1213
1314
pub mod environment;
1415
pub mod environment_name;
1516
pub mod instance_name;
17+
pub mod profile_name;
1618
pub mod template;
1719

1820
// Re-export commonly used domain types for convenience
1921
pub use environment::Environment;
2022
pub use environment_name::{EnvironmentName, EnvironmentNameError};
2123
pub use instance_name::{InstanceName, InstanceNameError};
24+
pub use profile_name::{ProfileName, ProfileNameError};
2225
pub use template::{TemplateEngine, TemplateEngineError, TemplateManager, TemplateManagerError};

0 commit comments

Comments
 (0)