manta_server/backend_dispatcher/
cfs.rs

1//! [`CfsTrait`] impl for [`StaticBackendDispatcher`].
2//!
3//! Forwards to the Configuration Framework Service (CFS) v3 API
4//! (`/apis/cfs/v3/{healthz,sessions,configurations,components}`) and
5//! its companion Kubernetes log-tailing path on CSM. The Ochami
6//! backend has an empty `impl CfsTrait for Ochami {}`, so every
7//! method on this trait goes through the trait default and returns
8//! [`Error::Message`] (`"... command not implemented for this
9//! backend"`) when the dispatcher is the Ochami variant.
10
11use super::*;
12
13impl CfsTrait for StaticBackendDispatcher {
14  /// Associated buffered reader type returned by the session-log
15  /// streaming methods. Pin-boxed and `Send` so it can cross await
16  /// points and be fed straight into the HTTP layer.
17  type T = Pin<Box<dyn AsyncBufRead + Send>>;
18
19  /// `GET /cfs/healthz` — liveness probe.
20  async fn get_cfs_health(&self) -> Result<(), Error> {
21    dispatch!(self, get_cfs_health)
22  }
23
24  /// Tail the Ansible-container log of `cfs_session_name`. Opens a
25  /// `kubectl logs --follow` style stream against the session pod in
26  /// the CSM `services` namespace via the supplied `k8s` context.
27  async fn get_session_logs_stream(
28    &self,
29    token: &str,
30    site_name: &str,
31    cfs_session_name: &str,
32    timestamps: bool,
33    k8s: &K8sDetails,
34  ) -> Result<Pin<Box<dyn AsyncBufRead + Send>>, Error> {
35    dispatch!(
36      self,
37      get_session_logs_stream,
38      token,
39      site_name,
40      cfs_session_name,
41      timestamps,
42      k8s
43    )
44  }
45
46  /// Resolve `xname` to its most recent CFS session and tail that
47  /// session's pod log. Convenience for "what is happening on this
48  /// node right now"; otherwise identical to
49  /// [`get_session_logs_stream`](Self::get_session_logs_stream).
50  async fn get_session_logs_stream_by_xname(
51    &self,
52    auth_token: &str,
53    site_name: &str,
54    xname: &str,
55    timestamps: bool,
56    k8s: &K8sDetails,
57  ) -> Result<Pin<Box<dyn AsyncBufRead + Send>>, Error> {
58    dispatch!(
59      self,
60      get_session_logs_stream_by_xname,
61      auth_token,
62      site_name,
63      xname,
64      timestamps,
65      k8s
66    )
67  }
68
69  /// `POST /cfs/v3/sessions` — submit `session` for execution.
70  /// Returns the backend's representation of the created session.
71  async fn post_session(
72    &self,
73    token: &str,
74    session: &CfsSessionPostRequest,
75  ) -> Result<CfsSessionGetResponse, Error> {
76    dispatch!(self, post_session, token, session)
77  }
78
79  /// `GET /cfs/v3/sessions` — server-side filtered list. Filters
80  /// match CFS v3 query parameters verbatim (`name`, `limit`,
81  /// `after_id`, `min_age`, `max_age`, `status`, `name_contains`,
82  /// `succeeded`, `tags`).
83  async fn get_sessions(
84    &self,
85    auth_token: &str,
86    session_name_opt: Option<&String>,
87    limit_opt: Option<u8>,
88    after_id_opt: Option<String>,
89    min_age_opt: Option<String>,
90    max_age_opt: Option<String>,
91    status_opt: Option<String>,
92    name_contains_opt: Option<String>,
93    is_succeded_opt: Option<bool>,
94    tags_opt: Option<String>,
95  ) -> Result<Vec<CfsSessionGetResponse>, Error> {
96    dispatch!(
97      self,
98      get_sessions,
99      auth_token,
100      session_name_opt,
101      limit_opt,
102      after_id_opt,
103      min_age_opt,
104      max_age_opt,
105      status_opt,
106      name_contains_opt,
107      is_succeded_opt,
108      tags_opt
109    )
110  }
111
112  /// Fetch sessions and apply client-side group/xname filtering that
113  /// CFS doesn't natively support. The backend issues the broadest
114  /// query CFS allows, then narrows the result set in-process to
115  /// `group_name_vec` / `xname_vec` / `type_opt`.
116  async fn get_and_filter_sessions(
117    &self,
118    token: &str,
119    group_name_vec: Vec<String>,
120    xname_vec: Vec<&str>,
121    min_age_opt: Option<&String>,
122    max_age_opt: Option<&String>,
123    type_opt: Option<&String>,
124    status_opt: Option<&String>,
125    cfs_session_name_opt: Option<&String>,
126    limit_number_opt: Option<&u8>,
127    is_succeded_opt: Option<bool>,
128  ) -> Result<Vec<CfsSessionGetResponse>, Error> {
129    dispatch!(
130      self,
131      get_and_filter_sessions,
132      token,
133      group_name_vec,
134      xname_vec,
135      min_age_opt,
136      max_age_opt,
137      type_opt,
138      status_opt,
139      cfs_session_name_opt,
140      limit_number_opt,
141      is_succeded_opt
142    )
143  }
144
145  /// Cancel an in-flight session (PATCH `status=cancelled`) and then
146  /// delete it along with derived BSS/CFS-component state, gated by
147  /// `group_available_vec` (RBAC) and `dry_run`. The backend
148  /// validates `cfs_session` against the supplied components and
149  /// bootparameters before mutating anything.
150  async fn delete_and_cancel_session(
151    &self,
152    token: &str,
153    group_available_vec: &[Group],
154    cfs_session: &CfsSessionGetResponse,
155    cfs_component_vec: &[CfsComponent],
156    bss_bootparameter_vec: &[BootParameters],
157    dry_run: bool,
158  ) -> Result<(), Error> {
159    dispatch!(
160      self,
161      delete_and_cancel_session,
162      token,
163      group_available_vec,
164      cfs_session,
165      cfs_component_vec,
166      bss_bootparameter_vec,
167      dry_run
168    )
169  }
170
171  /// Build (but do not POST) a `CfsConfigurationRequest` from Gitea
172  /// repos pinned to specific commit ids. Used by callers that want
173  /// to inspect or further edit the configuration before applying it.
174  async fn create_configuration_from_repos(
175    &self,
176    gitea_token: &str,
177    gitea_base_url: &str,
178    repo_name_vec: &[&str],
179    local_git_commit_vec: &[&str],
180    playbook_file_name_opt: Option<&str>,
181  ) -> Result<CfsConfigurationRequest, Error> {
182    dispatch!(
183      self,
184      create_configuration_from_repos,
185      gitea_token,
186      gitea_base_url,
187      repo_name_vec,
188      local_git_commit_vec,
189      playbook_file_name_opt
190    )
191  }
192
193  /// `GET /cfs/v3/configurations` — single configuration when
194  /// `cfs_configuration_name_opt` is `Some`, otherwise the full list.
195  async fn get_configuration(
196    &self,
197    auth_token: &str,
198    cfs_configuration_name_opt: Option<&String>,
199  ) -> Result<Vec<CfsConfigurationResponse>, Error> {
200    dispatch!(
201      self,
202      get_configuration,
203      auth_token,
204      cfs_configuration_name_opt
205    )
206  }
207
208  /// List configurations, optionally narrowed by exact name, regex,
209  /// the groups the caller can see, a date window, and a result
210  /// count cap. Name/group filtering happens server-side where CFS
211  /// supports it and client-side otherwise.
212  async fn get_and_filter_configuration(
213    &self,
214    auth_token: &str,
215    configuration_name: Option<&str>,
216    configuration_name_pattern: Option<&str>,
217    group_name_vec: &[String],
218    since_opt: Option<NaiveDateTime>,
219    until_opt: Option<NaiveDateTime>,
220    limit_number_opt: Option<&u8>,
221  ) -> Result<Vec<CfsConfigurationResponse>, Error> {
222    dispatch!(
223      self,
224      get_and_filter_configuration,
225      auth_token,
226      configuration_name,
227      configuration_name_pattern,
228      group_name_vec,
229      since_opt,
230      until_opt,
231      limit_number_opt
232    )
233  }
234
235  /// Resolve a single configuration `Layer` (Gitea repo + commit or
236  /// branch + playbook) to its enriched `LayerDetails` — adds the
237  /// most recent commit metadata, the resolved commit when only a
238  /// branch was given, and any tag the commit carries.
239  async fn get_configuration_layer_details(
240    &self,
241    gitea_base_url: &str,
242    gitea_token: &str,
243    layer: Layer,
244    site_name: &str,
245  ) -> Result<LayerDetails, Error> {
246    dispatch!(
247      self,
248      get_configuration_layer_details,
249      gitea_base_url,
250      gitea_token,
251      layer,
252      site_name
253    )
254  }
255
256  /// `PATCH /cfs/v3/components` — set
257  /// `desired_configuration` on each `xnames[]` entry and toggle the
258  /// component `enabled` flag. This is the per-node "runtime
259  /// reconfigure" knob (next CFS pass picks the change up).
260  async fn update_runtime_configuration(
261    &self,
262    auth_token: &str,
263    xnames: &[String],
264    desired_configuration: &str,
265    enabled: bool,
266  ) -> Result<(), Error> {
267    dispatch!(
268      self,
269      update_runtime_configuration,
270      auth_token,
271      xnames,
272      desired_configuration,
273      enabled
274    )
275  }
276
277  /// `PUT /cfs/v3/configurations/{name}` — create or replace
278  /// `configuration_name`. When `overwrite` is false and the
279  /// configuration already exists, the backend returns
280  /// [`Error::ConfigurationAlreadyExistsError`].
281  async fn put_configuration(
282    &self,
283    token: &str,
284    configuration: &CfsConfigurationRequest,
285    configuration_name: &str,
286    overwrite: bool,
287  ) -> Result<CfsConfigurationResponse, Error> {
288    dispatch!(
289      self,
290      put_configuration,
291      token,
292      configuration,
293      configuration_name,
294      overwrite
295    )
296  }
297
298  /// Find every artifact that references `configuration_name`:
299  /// sessions that ran against it, BOS templates that pin it, and
300  /// IMS images stamped with it. Each tuple slot is `None` when the
301  /// corresponding lookup returned an empty result so callers can
302  /// distinguish "no derivatives" from "lookup not performed".
303  async fn get_derivatives(
304    &self,
305    auth_token: &str,
306    configuration_name: &str,
307  ) -> Result<
308    (
309      Option<Vec<CfsSessionGetResponse>>,
310      Option<Vec<BosSessionTemplate>>,
311      Option<Vec<Image>>,
312    ),
313    Error,
314  > {
315    dispatch!(self, get_derivatives, auth_token, configuration_name)
316  }
317
318  /// `GET /cfs/v3/components` — per-xname CFS state filtered by
319  /// `configuration_name`, an `ids=` xname list, and/or `status`
320  /// (e.g. `pending`, `failed`, `configured`).
321  async fn get_cfs_components(
322    &self,
323    token: &str,
324    configuration_name: Option<&str>,
325    components_ids: Option<&str>,
326    status: Option<&str>,
327  ) -> Result<Vec<CfsComponent>, Error> {
328    dispatch!(
329      self,
330      get_cfs_components,
331      token,
332      configuration_name,
333      components_ids,
334      status
335    )
336  }
337}