manta_server/service/
configuration.rs

1//! CFS configuration queries, layer-detail lookups, and cascading deletion of
2//! all dependent resources (sessions, BOS templates, IMS images).
3
4use manta_backend_dispatcher::error::Error;
5use chrono::NaiveDateTime;
6use manta_backend_dispatcher::interfaces::cfs::CfsTrait;
7use manta_backend_dispatcher::interfaces::delete_configurations_and_data_related::DeleteConfigurationsAndDataRelatedTrait;
8use manta_backend_dispatcher::types::cfs::cfs_configuration_response::CfsConfigurationResponse;
9use manta_backend_dispatcher::types::cfs::session::CfsSessionGetResponse;
10
11use crate::server::common::app_context::InfraContext;
12use crate::server::common::authorization::get_groups_names_available;
13pub use manta_shared::shared::params::configuration::GetConfigurationParams;
14
15/// Fetch and filter CFS configurations from the backend.
16pub async fn get_configurations(
17  infra: &InfraContext<'_>,
18  token: &str,
19  params: &GetConfigurationParams,
20) -> Result<Vec<CfsConfigurationResponse>, Error> {
21  let target_hsm_group_vec = get_groups_names_available(
22    infra.backend,
23    token,
24    params.hsm_group.as_deref(),
25    params.settings_hsm_group_name.as_deref(),
26  )
27  .await?;
28
29  let limit_ref = params.limit.as_ref();
30
31  let cfs_configuration_vec = infra
32    .backend
33    .get_and_filter_configuration(
34      token,
35      infra.shasta_base_url,
36      infra.shasta_root_cert,
37      params.name.as_deref(),
38      params.pattern.as_deref(),
39      &target_hsm_group_vec,
40      params.since,
41      params.until,
42      limit_ref,
43    )
44    .await?;
45
46  Ok(cfs_configuration_vec)
47}
48
49/// Data gathered for deletion review and execution.
50#[derive(serde::Serialize)]
51pub struct DeletionCandidates {
52  /// CFS sessions whose desired-config matches a candidate configuration.
53  pub cfs_sessions_to_delete: Vec<CfsSessionGetResponse>,
54  /// BOS session templates to delete: `(name, cfs_config, description)`.
55  pub bos_sessiontemplate_tuples: Vec<(String, String, String)>,
56  /// IMS image IDs to delete (built by the matching sessions).
57  pub image_ids: Vec<String>,
58  /// Names of the configurations selected for deletion.
59  pub configuration_names: Vec<String>,
60  /// CFS sessions summary tuples: `(name, config_name, status)`.
61  pub cfs_session_tuples: Vec<(String, String, String)>,
62  /// Full configuration objects selected for deletion.
63  pub configurations: Vec<CfsConfigurationResponse>,
64}
65
66/// Fetch deletion candidates (no side effects).
67pub async fn get_deletion_candidates(
68  infra: &InfraContext<'_>,
69  token: &str,
70  settings_hsm_group_name_opt: Option<&str>,
71  configuration_name_pattern: Option<&str>,
72  since: Option<NaiveDateTime>,
73  until: Option<NaiveDateTime>,
74) -> Result<DeletionCandidates, Error> {
75  validate_date_range(since, until)?;
76
77  let target_hsm_group_vec =
78    if let Some(settings_hsm_group_name) = settings_hsm_group_name_opt {
79      vec![settings_hsm_group_name.to_string()]
80    } else {
81      get_groups_names_available(
82        infra.backend,
83        token,
84        None,
85        settings_hsm_group_name_opt,
86      )
87      .await?
88    };
89
90  let (
91    cfs_sessions_to_delete,
92    bos_sessiontemplate_tuples,
93    image_ids,
94    configuration_names,
95    cfs_session_tuples,
96    configurations,
97  ) = infra
98    .backend
99    .get_data_to_delete(
100      token,
101      infra.shasta_base_url,
102      infra.shasta_root_cert,
103      &target_hsm_group_vec,
104      configuration_name_pattern,
105      since,
106      until,
107    )
108    .await?;
109
110  Ok(DeletionCandidates {
111    cfs_sessions_to_delete,
112    bos_sessiontemplate_tuples,
113    image_ids,
114    configuration_names,
115    cfs_session_tuples,
116    configurations,
117  })
118}
119
120/// Validate that a `(since, until)` date range is well-ordered.
121///
122/// Extracted so the HTTP handler and CLI can share the check without
123/// constructing a full backend context.
124pub fn validate_date_range(
125  since: Option<NaiveDateTime>,
126  until: Option<NaiveDateTime>,
127) -> Result<(), Error> {
128  if let (Some(s), Some(u)) = (since, until)
129    && s > u
130  {
131    return Err(Error::BadRequest(
132      "'since' date can't be after 'until' date".to_string(),
133    ));
134  }
135  Ok(())
136}
137
138/// Execute the deletion of configurations and derivatives.
139pub async fn delete_configurations_and_derivatives(
140  infra: &InfraContext<'_>,
141  token: &str,
142  candidates: &DeletionCandidates,
143) -> Result<(), Error> {
144  let cfs_session_name_vec: Vec<String> = candidates
145    .cfs_session_tuples
146    .iter()
147    .map(|(session, _, _)| session.clone())
148    .collect();
149
150  let bos_sessiontemplate_name_vec: Vec<String> = candidates
151    .bos_sessiontemplate_tuples
152    .iter()
153    .map(|(sessiontemplate, _, _)| sessiontemplate.clone())
154    .collect();
155
156  infra
157    .backend
158    .delete(
159      token,
160      infra.shasta_base_url,
161      infra.shasta_root_cert,
162      &candidates.configuration_names,
163      &candidates.image_ids,
164      &cfs_session_name_vec,
165      &bos_sessiontemplate_name_vec,
166    )
167    .await?;
168
169  Ok(())
170}
171
172#[cfg(test)]
173mod tests {
174  use super::*;
175  use chrono::NaiveDateTime;
176
177  fn dt(s: &str) -> NaiveDateTime {
178    NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S").unwrap()
179  }
180
181  #[test]
182  fn validate_date_range_ok_when_since_before_until() {
183    assert!(
184      validate_date_range(
185        Some(dt("2024-01-01T00:00:00")),
186        Some(dt("2024-01-02T00:00:00"))
187      )
188      .is_ok()
189    );
190  }
191
192  #[test]
193  fn validate_date_range_ok_when_equal() {
194    let d = dt("2024-01-01T00:00:00");
195    assert!(validate_date_range(Some(d), Some(d)).is_ok());
196  }
197
198  #[test]
199  fn validate_date_range_ok_when_either_none() {
200    let d = dt("2024-01-01T00:00:00");
201    assert!(validate_date_range(Some(d), None).is_ok());
202    assert!(validate_date_range(None, Some(d)).is_ok());
203    assert!(validate_date_range(None, None).is_ok());
204  }
205
206  #[test]
207  fn validate_date_range_err_when_since_after_until() {
208    let result = validate_date_range(
209      Some(dt("2024-01-02T00:00:00")),
210      Some(dt("2024-01-01T00:00:00")),
211    );
212    assert!(result.is_err());
213    assert!(
214      result
215        .unwrap_err()
216        .to_string()
217        .contains("'since' date can't be after 'until' date")
218    );
219  }
220}