stormchaser_engine/
artifact.rs1use anyhow::{Context, Result};
2use serde_json::Value;
3use std::time::Duration;
4use stormchaser_model::dsl::Artifact;
5use stormchaser_model::storage::{BackendType, StorageBackend};
6use uuid::Uuid;
7
8pub async fn generate_parking_instructions(
10 backend: &StorageBackend,
11 run_id: Uuid,
12 storage_name: &str,
13 artifact: &Artifact,
14) -> Result<Value> {
15 match backend.backend_type {
16 BackendType::S3 => {
17 let client = crate::s3::get_s3_client(backend).await?;
18 let bucket = backend.config["bucket"]
19 .as_str()
20 .context("Missing bucket in SFS backend config")?;
21
22 let artifact_key = format!("artifacts/{}/{}/{}", run_id, storage_name, artifact.name);
23 let expires = Duration::from_secs(3600); let put_url =
26 crate::s3::generate_presigned_url(&client, bucket, &artifact_key, true, expires)
27 .await?;
28
29 Ok(serde_json::json!({
30 "backend_type": BackendType::S3,
31 "put_url": put_url,
32 "path": artifact.path,
33 "retention": artifact.retention,
34 "remote_path": artifact_key,
35 }))
36 }
37 BackendType::Oci => {
38 let registry = backend.config["registry"]
39 .as_str()
40 .context("Missing registry in OCI backend config")?;
41 let username = backend.config["username"].as_str();
42 let password = backend.config["password"].as_str();
43
44 let remote_path = format!("{}/{}/{}:{}", registry, run_id, storage_name, artifact.name);
45
46 let mut payload = serde_json::json!({
47 "backend_type": "oci",
48 "registry": registry,
49 "remote_path": remote_path,
50 "path": artifact.path,
51 "retention": artifact.retention,
52 });
53
54 if let (Some(u), Some(p)) = (username, password) {
55 if let Some(map) = payload.as_object_mut() {
56 map.insert("username".to_string(), Value::String(u.to_string()));
57 map.insert("password".to_string(), Value::String(p.to_string()));
58 }
59 }
60
61 Ok(payload)
62 }
63 _ => anyhow::bail!(
64 "Unsupported artifact backend type: {:?}",
65 backend.backend_type
66 ),
67 }
68}
69
70#[cfg(test)]
71mod tests {
72 use super::*;
73 use serde_json::json;
74 use stormchaser_model::BackendId;
75
76 #[tokio::test]
77 async fn test_generate_parking_instructions_oci() {
78 let backend = StorageBackend {
79 id: BackendId::new_v4(),
80 name: "oci-registry".into(),
81 description: None,
82 backend_type: BackendType::Oci,
83 config: json!({
84 "registry": "registry.paninfracon.net",
85 "username": "user",
86 "password": "pass"
87 }),
88 aws_assume_role_arn: None,
89 is_default_sfs: false,
90 created_at: chrono::Utc::now(),
91 updated_at: chrono::Utc::now(),
92 ca_cert: None,
93 client_cert: None,
94 client_key: None,
95 };
96
97 let run_id = Uuid::new_v4();
98 let artifact = Artifact {
99 name: "build-bin".into(),
100 path: "bin/".into(),
101 retention: "7d".into(),
102 };
103
104 let instructions = generate_parking_instructions(&backend, run_id, "my-storage", &artifact)
105 .await
106 .unwrap();
107
108 assert_eq!(instructions["backend_type"], "oci");
109 assert_eq!(instructions["registry"], "registry.paninfracon.net");
110 assert_eq!(instructions["username"], "user");
111 assert_eq!(instructions["password"], "pass");
112 assert!(instructions["remote_path"]
113 .as_str()
114 .unwrap()
115 .contains("registry.paninfracon.net"));
116 }
117
118 #[tokio::test]
119 async fn test_generate_parking_instructions_unsupported() {
120 let backend = StorageBackend {
121 id: BackendId::new_v4(),
122 name: "fs-backend".into(),
123 description: None,
124 backend_type: BackendType::Jfrog,
125 config: json!({}),
126 aws_assume_role_arn: None,
127 is_default_sfs: false,
128 created_at: chrono::Utc::now(),
129 updated_at: chrono::Utc::now(),
130 ca_cert: None,
131 client_cert: None,
132 client_key: None,
133 };
134
135 let result = generate_parking_instructions(
136 &backend,
137 Uuid::new_v4(),
138 "st",
139 &Artifact {
140 name: "n".into(),
141 path: "p".into(),
142 retention: "1d".into(),
143 },
144 )
145 .await;
146
147 assert!(result.is_err());
148 }
149}