manta_server/service/
ephemeral_env.rs

1//! Ephemeral CFS environment provisioning — launches a temporary
2//! container booted from an existing IMS image and returns its
3//! hostname.
4
5use csm_rs::ShastaClient;
6use manta_backend_dispatcher::error::Error;
7
8use crate::server::common::app_context::InfraContext;
9use crate::server::common::jwt_ops;
10use crate::wire_conv;
11
12const EPHEMERAL_IMAGE_NAME: &str = "__ephemeral_image";
13
14/// Launch an ephemeral IMS customize container against `image_id` and
15/// return its SSH hostname.
16///
17/// The caller's preferred username is read from the JWT and used to
18/// look up their registered SSH public key in IMS. If no key is
19/// registered, returns `NotFound` with a message pointing the user
20/// at platform admins. The hostname is plucked from the IMS response
21/// at `/ssh_containers/0/connection_info/customer_access/host`; a
22/// missing field is reported as `MissingField` rather than a generic
23/// error so operators can tell schema drift from real failures.
24pub async fn exec(
25  infra: &InfraContext<'_>,
26  token: &str,
27  image_id: &str,
28) -> Result<String, Error> {
29  let user_public_key_name =
30    jwt_ops::get_preferred_username(token).map_err(wire_conv::to_backend)?;
31
32  tracing::info!("Looking for user '{}' public SSH key", user_public_key_name);
33
34  let shasta = ShastaClient::new(
35    infra.shasta_base_url,
36    infra.shasta_root_cert.to_vec(),
37    infra.socks5_proxy.map(|s| s.to_string()),
38  )
39  .map_err(|e| {
40    Error::BadRequest(format!("Could not build Shasta HTTP client: {e}"))
41  })?;
42
43  let user_public_ssh_id = if let Ok(Some(user_public_ssh_key)) = shasta
44    .ims_public_keys_v3_get_single(token, &user_public_key_name)
45    .await
46  {
47    user_public_ssh_key.id.ok_or_else(|| {
48      Error::MissingField(
49        "IMS public-key response missing server-generated 'id'".to_string(),
50      )
51    })?
52  } else {
53    return Err(Error::NotFound(format!(
54      "User '{user_public_key_name}' does not have an SSH public key in Alps. \
55       Please contact platform sys admins."
56    )));
57  };
58
59  tracing::info!("SSH key found with ID {}", user_public_ssh_id);
60  tracing::info!(
61    "Creating ephemeral environment based on image ID {}",
62    image_id
63  );
64
65  let resp_json = shasta
66    .ims_job_post_customize(
67      token,
68      EPHEMERAL_IMAGE_NAME,
69      image_id,
70      &user_public_ssh_id,
71    )
72    .await
73    .map_err(|e| {
74      Error::BadRequest(format!(
75        "Could not create ephemeral environment based on image ID {image_id}: {e}"
76      ))
77    })?;
78
79  let hostname = resp_json
80    .pointer("/ssh_containers/0/connection_info/customer_access/host")
81    .and_then(|v| v.as_str())
82    .ok_or_else(|| {
83      Error::MissingField(
84        "Failed to get SSH container hostname from ephemeral env response"
85          .to_string(),
86      )
87    })?
88    .to_string();
89
90  tracing::info!("Ephemeral environment created — SSH hostname: {}", hostname);
91
92  Ok(hostname)
93}