-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathansible.rs
More file actions
188 lines (173 loc) · 6.4 KB
/
Copy pathansible.rs
File metadata and controls
188 lines (173 loc) · 6.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
//! Ansible Template Rendering Service
//!
//! This service is responsible for rendering Ansible templates with runtime
//! configuration. It's used by multiple command handlers (Provision, Register)
//! to prepare Ansible inventory and playbook files before configuration.
//!
//! ## Usage
//!
//! The service is injected with its dependencies (template renderer) at construction
//! time and receives only the data needed to render templates at execution time.
//!
//! ```rust,ignore
//! use torrust_tracker_deployer_lib::application::services::rendering::AnsibleTemplateRenderingService;
//!
//! // Create service with dependencies
//! let service = AnsibleTemplateRenderingService::from_paths(
//! templates_dir,
//! build_dir,
//! clock,
//! );
//!
//! // Render templates with user inputs and instance IP
//! service.render_templates(&user_inputs, instance_ip, None).await?;
//! ```
use std::net::{IpAddr, SocketAddr};
use std::path::PathBuf;
use std::sync::Arc;
use thiserror::Error;
use tracing::info;
use crate::application::steps::RenderAnsibleTemplatesStep;
use crate::domain::environment::UserInputs;
use crate::domain::TemplateManager;
use crate::infrastructure::templating::ansible::AnsibleProjectGenerator;
use crate::shared::clock::Clock;
/// Errors that can occur during Ansible template rendering
#[derive(Error, Debug)]
pub enum AnsibleTemplateRenderingServiceError {
/// Template rendering failed
#[error("Failed to render Ansible templates: {reason}")]
RenderingFailed {
/// Detailed reason for the failure
reason: String,
},
}
/// Service for rendering Ansible templates with runtime configuration
///
/// This service encapsulates the logic for rendering Ansible inventory and
/// configuration templates. It's designed to be shared across command handlers
/// that need to prepare Ansible files (e.g., Provision, Register).
///
/// ## Design
///
/// The service follows dependency injection principles:
/// - Dependencies (template renderer) are injected at construction time
/// - Runtime data (SSH credentials, IP, port) is passed to the render method
///
/// This allows the service to be configured once and reused with different
/// runtime parameters.
pub struct AnsibleTemplateRenderingService {
ansible_template_renderer: Arc<AnsibleProjectGenerator>,
clock: Arc<dyn Clock>,
}
impl AnsibleTemplateRenderingService {
/// Create a new `AnsibleTemplateRenderingService`
///
/// # Arguments
///
/// * `ansible_template_renderer` - The renderer for Ansible templates
/// * `clock` - The clock for generating timestamps
#[must_use]
pub fn new(
ansible_template_renderer: Arc<AnsibleProjectGenerator>,
clock: Arc<dyn Clock>,
) -> Self {
Self {
ansible_template_renderer,
clock,
}
}
/// Build an `AnsibleTemplateRenderingService` from environment paths
///
/// This is a factory method that creates the service with all necessary
/// dependencies based on the environment's template and build directories.
///
/// # Arguments
///
/// * `templates_dir` - Directory containing the source templates
/// * `build_dir` - Directory where rendered templates will be written
/// * `clock` - The clock for generating timestamps
///
/// # Returns
///
/// Returns a configured `AnsibleTemplateRenderingService` ready for template rendering
///
/// # Example
///
/// ```rust,ignore
/// use std::path::PathBuf;
/// use std::sync::Arc;
/// use torrust_tracker_deployer_lib::application::services::rendering::AnsibleTemplateRenderingService;
/// use torrust_tracker_deployer_lib::shared::clock::SystemClock;
///
/// let service = AnsibleTemplateRenderingService::from_paths(
/// PathBuf::from("templates"),
/// PathBuf::from("build/my-env"),
/// Arc::new(SystemClock),
/// );
/// ```
#[must_use]
pub fn from_paths(templates_dir: PathBuf, build_dir: PathBuf, clock: Arc<dyn Clock>) -> Self {
let template_manager = Arc::new(TemplateManager::new(templates_dir));
let ansible_template_renderer =
Arc::new(AnsibleProjectGenerator::new(build_dir, template_manager));
Self::new(ansible_template_renderer, clock)
}
/// Render Ansible templates with the provided runtime configuration
///
/// This renders the Ansible inventory and configuration templates so that
/// Ansible playbooks can be executed against the target instance.
///
/// # Arguments
///
/// * `user_inputs` - User-provided environment configuration (SSH credentials, tracker config, etc.)
/// * `instance_ip` - IP address of the provisioned instance (runtime output)
/// * `ssh_port_override` - Optional SSH port override (takes precedence over `user_inputs.ssh_port`)
///
/// # Errors
///
/// Returns `AnsibleTemplateRenderingServiceError::RenderingFailed` if template rendering fails.
///
/// # Example
///
/// ```rust,ignore
/// use std::net::IpAddr;
///
/// let service = AnsibleTemplateRenderingService::from_paths(...);
/// service.render_templates(&user_inputs, "192.168.1.100".parse().unwrap(), None).await?;
/// ```
pub async fn render_templates(
&self,
user_inputs: &UserInputs,
instance_ip: IpAddr,
ssh_port_override: Option<u16>,
) -> Result<(), AnsibleTemplateRenderingServiceError> {
let effective_ssh_port = ssh_port_override.unwrap_or(user_inputs.ssh_port());
info!(
instance_ip = %instance_ip,
ssh_port = effective_ssh_port,
ssh_port_override = ?ssh_port_override,
"Rendering Ansible templates"
);
let ssh_socket_addr = SocketAddr::new(instance_ip, effective_ssh_port);
RenderAnsibleTemplatesStep::new(
self.ansible_template_renderer.clone(),
user_inputs.ssh_credentials().clone(),
ssh_socket_addr,
user_inputs.tracker().clone(),
user_inputs.grafana().cloned(),
self.clock.clone(),
)
.execute()
.await
.map_err(|e| AnsibleTemplateRenderingServiceError::RenderingFailed {
reason: e.to_string(),
})?;
info!(
instance_ip = %instance_ip,
ssh_port = effective_ssh_port,
"Ansible templates rendered successfully"
);
Ok(())
}
}