1313//! **Runtime Outputs** are:
1414//! - Generated during deployment operations
1515//! - Mutable as operations progress
16- //! - Examples: IP addresses, container IDs
16+ //! - Examples: IP addresses, container IDs, service URLs
1717//!
1818//! Add new fields here when: Operations produce new data about the deployed infrastructure.
1919//!
2222//! This struct is expected to grow with fields like:
2323//! - `container_id: Option<String>` - Container/VM identifier
2424//! - `resource_metrics: Option<ResourceMetrics>` - CPU, memory, disk usage
25- //! - `service_endpoints: Option<Vec<ServiceEndpoint>>` - HTTP/TCP service URLs
2625
2726use serde:: { Deserialize , Serialize } ;
2827use std:: net:: IpAddr ;
28+ use url:: Url ;
2929
3030/// How the infrastructure instance was provisioned
3131///
@@ -57,6 +57,136 @@ impl std::fmt::Display for ProvisionMethod {
5757 }
5858}
5959
60+ /// Service endpoints for deployed tracker services
61+ ///
62+ /// This struct stores the URLs for all deployed tracker services. These URLs
63+ /// are computed from the tracker configuration and instance IP after the
64+ /// `run` command successfully starts the services.
65+ ///
66+ /// # Purpose
67+ ///
68+ /// Having service endpoints as first-class data allows:
69+ /// - Displaying service URLs without recomputation
70+ /// - Sharing URLs with external tools/integrations
71+ /// - Validating service availability against stored endpoints
72+ ///
73+ /// # Examples
74+ ///
75+ /// ```rust
76+ /// use torrust_tracker_deployer_lib::domain::environment::runtime_outputs::ServiceEndpoints;
77+ /// use url::Url;
78+ ///
79+ /// let endpoints = ServiceEndpoints {
80+ /// udp_trackers: vec![
81+ /// Url::parse("udp://10.0.0.1:6969/announce").unwrap(),
82+ /// ],
83+ /// http_trackers: vec![
84+ /// Url::parse("http://10.0.0.1:7070/announce").unwrap(),
85+ /// ],
86+ /// api_endpoint: Some(Url::parse("http://10.0.0.1:1212/api").unwrap()),
87+ /// health_check_url: Some(Url::parse("http://10.0.0.1:1313/health_check").unwrap()),
88+ /// };
89+ /// ```
90+ #[ derive( Debug , Clone , Serialize , Deserialize ) ]
91+ pub struct ServiceEndpoints {
92+ /// UDP tracker announce URLs (e.g., `udp://10.0.0.1:6969/announce`)
93+ #[ serde( default ) ]
94+ pub udp_trackers : Vec < Url > ,
95+
96+ /// HTTP tracker announce URLs (e.g., `http://10.0.0.1:7070/announce`)
97+ #[ serde( default ) ]
98+ pub http_trackers : Vec < Url > ,
99+
100+ /// HTTP API endpoint URL (e.g., `http://10.0.0.1:1212/api`)
101+ pub api_endpoint : Option < Url > ,
102+
103+ /// Health check API URL (e.g., `http://10.0.0.1:1313/health_check`)
104+ pub health_check_url : Option < Url > ,
105+ }
106+
107+ impl ServiceEndpoints {
108+ /// Create new `ServiceEndpoints` from the provided URLs
109+ #[ must_use]
110+ pub fn new (
111+ udp_trackers : Vec < Url > ,
112+ http_trackers : Vec < Url > ,
113+ api_endpoint : Option < Url > ,
114+ health_check_url : Option < Url > ,
115+ ) -> Self {
116+ Self {
117+ udp_trackers,
118+ http_trackers,
119+ api_endpoint,
120+ health_check_url,
121+ }
122+ }
123+
124+ /// Build `ServiceEndpoints` from tracker configuration and instance IP
125+ ///
126+ /// Constructs service URLs by combining the configured bind addresses
127+ /// with the actual instance IP address.
128+ ///
129+ /// # Examples
130+ ///
131+ /// ```rust
132+ /// use torrust_tracker_deployer_lib::domain::environment::runtime_outputs::ServiceEndpoints;
133+ /// use torrust_tracker_deployer_lib::domain::tracker::TrackerConfig;
134+ /// use std::net::{IpAddr, Ipv4Addr};
135+ ///
136+ /// let tracker_config = TrackerConfig::default();
137+ /// let instance_ip = IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1));
138+ ///
139+ /// let endpoints = ServiceEndpoints::from_tracker_config(&tracker_config, instance_ip);
140+ /// ```
141+ #[ must_use]
142+ pub fn from_tracker_config (
143+ tracker_config : & crate :: domain:: tracker:: TrackerConfig ,
144+ instance_ip : IpAddr ,
145+ ) -> Self {
146+ let udp_trackers = tracker_config
147+ . udp_trackers
148+ . iter ( )
149+ . filter_map ( |udp| {
150+ Url :: parse ( & format ! (
151+ "udp://{}:{}/announce" ,
152+ instance_ip,
153+ udp. bind_address. port( )
154+ ) )
155+ . ok ( )
156+ } )
157+ . collect ( ) ;
158+
159+ let http_trackers = tracker_config
160+ . http_trackers
161+ . iter ( )
162+ . filter_map ( |http| {
163+ Url :: parse ( & format ! (
164+ "http://{}:{}/announce" , // DevSkim: ignore DS137138
165+ instance_ip,
166+ http. bind_address. port( )
167+ ) )
168+ . ok ( )
169+ } )
170+ . collect ( ) ;
171+
172+ let api_endpoint = Url :: parse ( & format ! (
173+ "http://{}:{}/api" , // DevSkim: ignore DS137138
174+ instance_ip,
175+ tracker_config. http_api. bind_address. port( )
176+ ) )
177+ . ok ( ) ;
178+
179+ let health_check_url = Url :: parse ( & format ! (
180+ "http://{}:{}/health_check" , // DevSkim: ignore DS137138
181+ instance_ip,
182+ tracker_config. health_check_api. bind_address. port( )
183+ ) )
184+ . ok ( ) ;
185+
186+ Self :: new ( udp_trackers, http_trackers, api_endpoint, health_check_url)
187+ }
188+ }
189+
60190/// Runtime outputs generated during deployment operations
61191///
62192/// This struct contains fields that are generated during deployment operations
@@ -68,7 +198,6 @@ impl std::fmt::Display for ProvisionMethod {
68198/// This struct is expected to grow as deployment operations become more complex:
69199/// - `container_id: Option<String>` - Container/VM identifier
70200/// - `resource_metrics: Option<ResourceMetrics>` - CPU, memory, disk usage
71- /// - `service_endpoints: Option<Vec<ServiceEndpoint>>` - HTTP/TCP service URLs
72201///
73202/// # Examples
74203///
@@ -79,6 +208,7 @@ impl std::fmt::Display for ProvisionMethod {
79208/// let runtime_outputs = RuntimeOutputs {
80209/// instance_ip: Some(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 100))),
81210/// provision_method: Some(ProvisionMethod::Provisioned),
211+ /// service_endpoints: None,
82212/// };
83213/// ```
84214#[ derive( Debug , Clone , Serialize , Deserialize ) ]
@@ -100,4 +230,14 @@ pub struct RuntimeOutputs {
100230 /// - `Some(Registered)`: Instance was connected via `register` command
101231 #[ serde( default ) ]
102232 pub provision_method : Option < ProvisionMethod > ,
233+
234+ /// Service endpoints populated after services are started
235+ ///
236+ /// This field stores the URLs for all deployed tracker services. It is
237+ /// populated by the `run` command after services start successfully.
238+ ///
239+ /// - `None`: Services not yet started or legacy state
240+ /// - `Some(endpoints)`: URLs for all running services
241+ #[ serde( default ) ]
242+ pub service_endpoints : Option < ServiceEndpoints > ,
103243}
0 commit comments