Skip to content

Commit dd82621

Browse files
authored
feat: Connect backend with CLI (#75)
* feat: connect CLI and backend and gitignore kernel file
1 parent feef5d5 commit dd82621

File tree

7 files changed

+95
-41
lines changed

7 files changed

+95
-41
lines changed

agent/src/main.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,7 @@ async fn main() -> Result<()> {
6666
)
6767
.init();
6868

69-
let server_addr =
70-
env::var("AGENT_SERVER_ADDR").unwrap_or_else(|_| "0.0.0.0:3001".to_string());
69+
let server_addr = env::var("AGENT_SERVER_ADDR").unwrap_or_else(|_| "0.0.0.0:3001".to_string());
7170
let work_dir = resolve_work_dir(PathBuf::from(
7271
env::var("AGENT_WORK_DIR").unwrap_or_else(|_| "build".to_string()),
7372
))?;

backend/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
**/tmp/
22
cloude-agentd
3+
vmlinux

backend/src/initramfs_manager.rs

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -167,23 +167,42 @@ impl InitramfsLanguage {
167167
) -> Result<bool, Error> {
168168
let out_mtime = fs::metadata(out_path)
169169
.and_then(|m| m.modified())
170-
.map_err(|e| Error::new(ErrorKind::Other, format!("failed to stat {}: {}", out_path.display(), e)))?;
170+
.map_err(|e| {
171+
Error::new(
172+
ErrorKind::Other,
173+
format!("failed to stat {}: {}", out_path.display(), e),
174+
)
175+
})?;
171176

172177
let agent_mtime = fs::metadata(agent_binary)
173178
.and_then(|m| m.modified())
174-
.map_err(|e| Error::new(ErrorKind::Other, format!("failed to stat agent binary '{}': {}", agent_binary, e)))?;
179+
.map_err(|e| {
180+
Error::new(
181+
ErrorKind::Other,
182+
format!("failed to stat agent binary '{}': {}", agent_binary, e),
183+
)
184+
})?;
175185

176186
let init_mtime = fs::metadata(init_script)
177187
.and_then(|m| m.modified())
178-
.map_err(|e| Error::new(ErrorKind::Other, format!("failed to stat init script '{}': {}", init_script, e)))?;
188+
.map_err(|e| {
189+
Error::new(
190+
ErrorKind::Other,
191+
format!("failed to stat init script '{}': {}", init_script, e),
192+
)
193+
})?;
179194

180195
let image_mismatch = match Self::read_build_metadata(out_path) {
181196
Ok(Some(previous_base_image)) => previous_base_image != base_image,
182197
Ok(None) => true,
183198
Err(e) => {
184199
return Err(Error::new(
185200
ErrorKind::Other,
186-
format!("failed to read build metadata for {}: {}", out_path.display(), e),
201+
format!(
202+
"failed to read build metadata for {}: {}",
203+
out_path.display(),
204+
e
205+
),
187206
));
188207
}
189208
};

backend/src/main.rs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,7 @@ async fn main() -> Result<(), std::io::Error> {
108108
env::var("AGENT_BINARY_PATH").unwrap_or_else(|_| "./cloude-agentd".to_string());
109109

110110
let init_script = env::var("INIT_SCRIPT_PATH").unwrap_or_else(|_| "./init.sh".to_string());
111-
let vm_initramfs_dir =
112-
env::var("VM_INITRAMFS_DIR").unwrap_or_else(|_| "./tmp".to_string());
111+
let vm_initramfs_dir = env::var("VM_INITRAMFS_DIR").unwrap_or_else(|_| "./tmp".to_string());
113112

114113
let available_languages: Vec<backend::initramfs_manager::InitramfsLanguage> =
115114
get_languages_config(&languages_config_path)?;
@@ -185,8 +184,7 @@ async fn main() -> Result<(), std::io::Error> {
185184
.build()
186185
.expect("Failed to build HTTP client");
187186

188-
let vm_kernel_path =
189-
env::var("VM_KERNEL_PATH").unwrap_or_else(|_| "./vmlinux".to_string());
187+
let vm_kernel_path = env::var("VM_KERNEL_PATH").unwrap_or_else(|_| "./vmlinux".to_string());
190188
let vm_log_guest_console = env::var("VM_LOG_GUEST_CONSOLE")
191189
.map(|v| {
192190
let normalized = v.trim().to_ascii_lowercase();
@@ -210,13 +208,19 @@ async fn main() -> Result<(), std::io::Error> {
210208
let host_space = 1_u32.checked_shl(host_bits).ok_or_else(|| {
211209
std::io::Error::new(
212210
std::io::ErrorKind::InvalidInput,
213-
format!("Failed to compute host address space from IP_MASK={}", ip_mask),
211+
format!(
212+
"Failed to compute host address space from IP_MASK={}",
213+
ip_mask
214+
),
214215
)
215216
})?;
216217
let broadcast_offset = host_space.checked_sub(1).ok_or_else(|| {
217218
std::io::Error::new(
218219
std::io::ErrorKind::InvalidInput,
219-
format!("Failed to compute broadcast offset from IP_MASK={}", ip_mask),
220+
format!(
221+
"Failed to compute broadcast offset from IP_MASK={}",
222+
ip_mask
223+
),
220224
)
221225
})?;
222226
let ip_range_u32 = u32::from(ip_range);

backend/src/vm_lifecycle.rs

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -238,19 +238,24 @@ impl VmHandle {
238238
}
239239

240240
/// Build initramfs with embedded agent binary
241-
async fn build_initramfs_with_agent(language: &str, config: &VmConfig) -> Result<PathBuf, VmError> {
241+
async fn build_initramfs_with_agent(
242+
language: &str,
243+
config: &VmConfig,
244+
) -> Result<PathBuf, VmError> {
242245
debug!(language = %language, "Resolving language initramfs");
243246

244247
let prefix = format!("{}-", language);
245248
let mut candidates: Vec<PathBuf> = Vec::new();
246249

247-
let mut entries = tokio::fs::read_dir(&config.initramfs_dir).await.map_err(|e| {
248-
VmError::InitramfsBuild(format!(
249-
"Failed to read initramfs dir '{}': {}",
250-
config.initramfs_dir.display(),
251-
e
252-
))
253-
})?;
250+
let mut entries = tokio::fs::read_dir(&config.initramfs_dir)
251+
.await
252+
.map_err(|e| {
253+
VmError::InitramfsBuild(format!(
254+
"Failed to read initramfs dir '{}': {}",
255+
config.initramfs_dir.display(),
256+
e
257+
))
258+
})?;
254259

255260
while let Some(entry) = entries.next_entry().await.map_err(|e| {
256261
VmError::InitramfsBuild(format!(

cli/src/main.rs

Lines changed: 40 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -118,13 +118,45 @@ async fn cmd_go(
118118
}
119119

120120
let run: RunResponse = resp.json().await?;
121-
println!("Job submitted successfully!");
122-
println!(" ID: {}", run.id);
123-
println!("\nCheck the result with:\n cloude status {}", run.id);
124-
Ok(())
121+
let job_id = run.id.clone();
122+
123+
loop {
124+
tokio::time::sleep(Duration::from_secs(1)).await;
125+
126+
let status_url = format!("{backend}/status/{job_id}");
127+
let status_resp = client.get(&status_url).send().await?;
128+
129+
if !status_resp.status().is_success() {
130+
let status = status_resp.status();
131+
let err: ErrorBody = status_resp.json().await.unwrap_or(ErrorBody {
132+
error: format!("HTTP {status}"),
133+
});
134+
return Err(format!("Backend error (HTTP {status}): {}", err.error).into());
135+
}
136+
137+
let st: StatusResponse = status_resp.json().await?;
138+
139+
if st.status == "done" || st.status == "error" {
140+
println!("Status: {}", st.status);
141+
if let Some(code) = st.exit_code {
142+
println!("Exit code: {code}");
143+
}
144+
if let Some(ref out) = st.stdout {
145+
if !out.is_empty() {
146+
println!("{out}");
147+
}
148+
}
149+
if let Some(ref err) = st.stderr {
150+
if !err.is_empty() {
151+
println!("{err}");
152+
}
153+
}
154+
return Ok(());
155+
}
156+
}
125157
}
126158

127-
// ── status: poll job result ────────────────────────────────────────
159+
// ── status: query job result ────────────────────────────────────────
128160

129161
async fn cmd_status(
130162
client: &reqwest::Client,
@@ -144,21 +176,10 @@ async fn cmd_status(
144176

145177
let st: StatusResponse = resp.json().await?;
146178

147-
println!("Job {}", st.id);
148-
println!(" Status: {}", st.status);
149-
179+
println!("Job ID: {}", st.id);
180+
println!("Status: {}", st.status);
150181
if let Some(code) = st.exit_code {
151-
println!(" Exit code: {code}");
152-
}
153-
if let Some(ref out) = st.stdout {
154-
if !out.is_empty() {
155-
println!(" ── stdout ──\n{out}");
156-
}
157-
}
158-
if let Some(ref err) = st.stderr {
159-
if !err.is_empty() {
160-
println!(" ── stderr ──\n{err}");
161-
}
182+
println!("Exit code: {code}");
162183
}
163184
Ok(())
164185
}

vmm/src/devices/stdin.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,9 @@ impl MutEventSubscriber for StdinHandler {
5252
}
5353
}
5454
Ok(0) => {
55-
if let Err(e) = ops.remove(Events::empty(&FdWrapper(self.input.as_raw_fd()))) {
55+
if let Err(e) =
56+
ops.remove(Events::empty(&FdWrapper(self.input.as_raw_fd())))
57+
{
5658
eprintln!("Failed to remove stdin event on EOF: {:?}", e);
5759
}
5860
}
@@ -74,7 +76,10 @@ impl MutEventSubscriber for StdinHandler {
7476
if let Err(e) = ops.add(Events::with_data(&wrapper, STDIN_DATA, EventSet::IN)) {
7577
// This can legitimately fail with EPERM for non-epollable fds (e.g. /dev/null).
7678
// Stdin forwarding is optional for backend-driven jobs, so keep running.
77-
eprintln!("Unable to add stdin event, disabling stdin forwarding: {:?}", e);
79+
eprintln!(
80+
"Unable to add stdin event, disabling stdin forwarding: {:?}",
81+
e
82+
);
7883
}
7984
}
8085
}

0 commit comments

Comments
 (0)