mur_core/workflow/
cloud.rs1use crate::types::ExecutionResult;
4use anyhow::{Context, Result};
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7
8pub struct CloudExecutor {
10 api_token: String,
11 base_url: String,
12 client: reqwest::Client,
13}
14
15#[derive(Debug, Serialize)]
17pub struct CloudRunRequest {
18 pub workflow_id: String,
19 pub variables: HashMap<String, String>,
20 pub shadow: bool,
21}
22
23#[derive(Debug, Deserialize)]
25pub struct CloudRunResponse {
26 pub execution_id: String,
27 pub status: String,
28 pub result: Option<ExecutionResult>,
29}
30
31impl CloudExecutor {
32 pub fn new(api_token: String, base_url: Option<String>) -> Self {
33 Self {
34 api_token,
35 base_url: base_url.unwrap_or_else(|| "https://mur.run/api/v1".into()),
36 client: reqwest::Client::new(),
37 }
38 }
39
40 pub async fn submit(&self, request: &CloudRunRequest) -> Result<CloudRunResponse> {
42 let response = self
43 .client
44 .post(format!("{}/workflows/run", self.base_url))
45 .header("Authorization", format!("Bearer {}", self.api_token))
46 .json(request)
47 .send()
48 .await
49 .context("Submitting to mur.run")?;
50
51 if !response.status().is_success() {
52 let status = response.status();
53 let body = response.text().await.unwrap_or_default();
54 anyhow::bail!("Cloud execution error {}: {}", status, body);
55 }
56
57 response.json().await.context("Parsing cloud response")
58 }
59
60 pub async fn status(&self, execution_id: &str) -> Result<CloudRunResponse> {
62 let response = self
63 .client
64 .get(format!("{}/executions/{}", self.base_url, execution_id))
65 .header("Authorization", format!("Bearer {}", self.api_token))
66 .send()
67 .await
68 .context("Checking cloud execution status")?;
69
70 response.json().await.context("Parsing status response")
71 }
72
73 pub async fn upload_workflow(&self, workflow_yaml: &str) -> Result<String> {
75 let response = self
76 .client
77 .post(format!("{}/workflows/upload", self.base_url))
78 .header("Authorization", format!("Bearer {}", self.api_token))
79 .header("Content-Type", "text/yaml")
80 .body(workflow_yaml.to_string())
81 .send()
82 .await
83 .context("Uploading workflow")?;
84
85 if !response.status().is_success() {
86 anyhow::bail!("Upload failed: {}", response.status());
87 }
88
89 #[derive(Deserialize)]
90 struct UploadResponse {
91 workflow_id: String,
92 }
93 let resp: UploadResponse = response.json().await?;
94 Ok(resp.workflow_id)
95 }
96}
97
98#[cfg(test)]
99mod tests {
100 use super::*;
101
102 #[test]
103 fn test_cloud_run_request() {
104 let req = CloudRunRequest {
105 workflow_id: "deploy".into(),
106 variables: HashMap::from([("env".into(), "staging".into())]),
107 shadow: true,
108 };
109 let json = serde_json::to_string(&req).unwrap();
110 assert!(json.contains("deploy"));
111 assert!(json.contains("shadow"));
112 }
113}