1use rocket::{delete, get, post, patch, put};
2use rocket::serde::{Serialize, Deserialize, json::Json};
3use rocket::State;
4use rocket::FromForm;
5use std::sync::{Arc, Mutex};
6use std::collections::HashMap;
7use bollard::Docker;
8use bollard::container::{CreateContainerOptions, Config, StartContainerOptions, StopContainerOptions, RemoveContainerOptions, ListContainersOptions};
9use bollard::image::ListImagesOptions;
10use bollard::system::EventsOptions;
11use futures::stream::{StreamExt, TryStreamExt};
12
13#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct AppInstance {
16 id: String,
17 name: String,
18 image: String,
19 status: String,
20 created_at: String,
21 ports: Vec<PortMapping>,
22 environment: HashMap<String, String>,
23 volumes: Vec<VolumeMapping>,
24 agent_id: String,
25}
26
27#[derive(Debug, Clone, Serialize, Deserialize)]
28pub struct PortMapping {
29 host_port: u16,
30 container_port: u16,
31 protocol: String,
32}
33
34#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct VolumeMapping {
36 host_path: String,
37 container_path: String,
38}
39#[derive(Debug, Clone, rocket::serde::Serialize, rocket::serde::Deserialize)]
40#[serde(crate = "rocket::serde")]
41pub struct AppInstanceRequest {
42 name: String,
43 image: String,
44 ports: Option<Vec<PortMapping>>,
45 environment: Option<HashMap<String, String>>,
46 volumes: Option<Vec<VolumeMapping>>,
47}
48
49pub struct AppManager {
51 docker: Docker,
52 instances: Arc<Mutex<HashMap<String, AppInstance>>>,
53}
54
55impl AppManager {
56 pub fn new() -> Result<Self, String> {
57 let docker = match Docker::connect_with_local_defaults() {
60 Ok(docker) => docker,
61 Err(e) => return Err(format!("Failed to connect to Docker: {}", e)),
62 };
63
64 Ok(AppManager {
65 docker,
66 instances: Arc::new(Mutex::new(HashMap::new())),
67 })
68 }
69}
70
71#[get("/instances")]
73pub async fn list_instances(app_manager: &State<AppManager>) -> Json<Vec<AppInstance>> {
74 let mut instances = Vec::new();
75
76 let options = Some(ListContainersOptions::<String> {
78 all: true,
79 ..Default::default()
80 });
81
82 match app_manager.docker.list_containers(options).await {
83 Ok(containers) => {
84 for container in containers {
85 if let (Some(id), Some(image), Some(names), Some(created), Some(status)) =
86 (container.id, container.image, container.names, container.created, container.status) {
87 if let Some(name) = names.first() {
88 let name = name.trim_start_matches('/').to_string();
89 let app_instance = AppInstance {
90 id: id.clone(),
91 name,
92 image,
93 status,
94 created_at: created.to_string(),
95 ports: Vec::new(), environment: HashMap::new(), volumes: Vec::new(), agent_id: "current".to_string(), };
100 instances.push(app_instance);
101 }
102 }
103 }
104 },
105 Err(e) => {
106 eprintln!("Failed to list containers: {}", e);
107 }
108 }
109
110 Json(instances)
111}
112
113#[get("/instances/<id>")]
114pub async fn get_instance(id: String, app_manager: &State<AppManager>) -> Option<Json<AppInstance>> {
115 match app_manager.docker.inspect_container(&id, None).await {
117 Ok(container) => {
118 let config = container.config?;
119 let state = container.state?;
120
121 let name = container.name?;
122 let name = name.trim_start_matches('/').to_string();
123
124 let app_instance = AppInstance {
125 id: container.id.unwrap_or(id),
126 name,
127 image: config.image.unwrap_or_default(),
128 status: state.status.map(|s| s.to_string()).unwrap_or_else(|| "unknown".to_string()),
129 created_at: container.created.unwrap_or_default(),
130 ports: Vec::new(), environment: HashMap::new(), volumes: Vec::new(), agent_id: "current".to_string(),
134 };
135
136 Some(Json(app_instance))
137 },
138 Err(_) => None
139 }
140}
141#[post("/instances", format = "json", data = "<app_req>")]
142pub async fn create_instance(app_req: Json<AppInstanceRequest>, app_manager: &State<AppManager>) -> Result<Json<AppInstance>, String> {
143 let name = app_req.name.clone();
145
146 let mut port_bindings = HashMap::new();
147 if let Some(ports) = &app_req.ports {
148 for port in ports {
149 let host_binding = format!("{}:{}", port.host_port, port.container_port);
150 port_bindings.insert(
151 format!("{}/{}", port.container_port, port.protocol),
152 Some(vec![bollard::models::PortBinding {
153 host_ip: Some("0.0.0.0".to_string()),
154 host_port: Some(port.host_port.to_string())
155 }])
156 );
157 }
158 }
159
160 let mut env_vars = Vec::new();
161 if let Some(env) = &app_req.environment {
162 for (key, value) in env {
163 env_vars.push(format!("{}={}", key, value));
164 }
165 }
166
167 let mut volume_bindings = Vec::new();
168 if let Some(volumes) = &app_req.volumes {
169 for volume in volumes {
170 volume_bindings.push(format!("{}:{}", volume.host_path, volume.container_path));
171 }
172 }
173
174 let options = Some(CreateContainerOptions {
176 name: &name,
177 platform: None,
178 });
179
180 let config = Config {
181 image: Some(app_req.image.clone()),
182 env: Some(env_vars),
183 exposed_ports: Some(HashMap::new()), host_config: Some(bollard::models::HostConfig {
185 port_bindings: Some(port_bindings),
186 binds: Some(volume_bindings),
187 ..Default::default()
188 }),
189 ..Default::default()
190 };
191
192 match app_manager.docker.create_container(options, config).await {
193 Ok(response) => {
194 let id = response.id;
196 match app_manager.docker.start_container(&id, None::<StartContainerOptions<String>>).await {
197 Ok(_) => {
198 let app_instance = AppInstance {
200 id: id.clone(),
201 name: app_req.name.clone(),
202 image: app_req.image.clone(),
203 status: "running".to_string(),
204 created_at: chrono::Utc::now().to_string(),
205 ports: app_req.ports.clone().unwrap_or_default(),
206 environment: app_req.environment.clone().unwrap_or_default(),
207 volumes: app_req.volumes.clone().unwrap_or_default(),
208 agent_id: "current".to_string(),
209 };
210
211 app_manager.instances.lock().unwrap().insert(id, app_instance.clone());
213
214 Ok(Json(app_instance))
215 },
216 Err(e) => Err(format!("Failed to start instance: {}", e))
217 }
218 },
219 Err(e) => Err(format!("Failed to create instance: {}", e))
220 }
221}
222
223#[put("/instances/<id>/start")]
224pub async fn start_instance(id: String, app_manager: &State<AppManager>) -> Result<Json<AppInstance>, String> {
225 match app_manager.docker.start_container(&id, None::<StartContainerOptions<String>>).await {
227 Ok(_) => {
228 match get_instance(id, app_manager).await {
230 Some(instance) => Ok(instance),
231 None => Err("Failed to get instance after starting".to_string())
232 }
233 },
234 Err(e) => Err(format!("Failed to start instance: {}", e))
235 }
236}
237
238#[put("/instances/<id>/stop")]
239pub async fn stop_instance(id: String, app_manager: &State<AppManager>) -> Result<Json<AppInstance>, String> {
240 let options = Some(StopContainerOptions {
242 t: 30, });
244
245 match app_manager.docker.stop_container(&id, options).await {
246 Ok(_) => {
247 match get_instance(id, app_manager).await {
249 Some(instance) => Ok(instance),
250 None => Err("Failed to get instance after stopping".to_string())
251 }
252 },
253 Err(e) => Err(format!("Failed to stop instance: {}", e))
254 }
255}
256
257#[put("/instances/<id>/restart")]
258pub async fn restart_instance(id: String, app_manager: &State<AppManager>) -> Result<Json<AppInstance>, String> {
259 let options = Some(bollard::container::RestartContainerOptions {
261 t: 30, });
263
264 match app_manager.docker.restart_container(&id, options).await {
265 Ok(_) => {
266 match get_instance(id, app_manager).await {
268 Some(instance) => Ok(instance),
269 None => Err("Failed to get instance after restarting".to_string())
270 }
271 },
272 Err(e) => Err(format!("Failed to restart instance: {}", e))
273 }
274}
275#[patch("/instances/<id>", format = "json", data = "<update_req>")]
276pub async fn update_instance(id: String, update_req: Json<AppInstanceRequest>, app_manager: &State<AppManager>) -> Result<Json<AppInstance>, String> {
277 let stop_result = stop_instance(id.clone(), app_manager).await;
288 if stop_result.is_err() {
289 return Err(format!("Failed to stop instance for update: {}", stop_result.err().unwrap()));
290 }
291
292 let options = Some(RemoveContainerOptions {
294 force: true,
295 ..Default::default()
296 });
297
298 match app_manager.docker.remove_container(&id, options).await {
299 Ok(_) => {
300 create_instance(update_req, app_manager).await
302 },
303 Err(e) => Err(format!("Failed to remove instance for update: {}", e))
304 }
305}
306
307#[delete("/instances/<id>")]
308pub async fn delete_instance(id: String, app_manager: &State<AppManager>) -> Result<String, String> {
309 let options = Some(RemoveContainerOptions {
311 force: true,
312 ..Default::default()
313 });
314
315 match app_manager.docker.remove_container(&id, options).await {
316 Ok(_) => {
317 app_manager.instances.lock().unwrap().remove(&id);
319 Ok(format!("Instance {} deleted successfully", id))
320 },
321 Err(e) => Err(format!("Failed to delete instance: {}", e))
322 }
323}
324
325#[get("/images")]
326pub async fn list_images(app_manager: &State<AppManager>) -> Json<Vec<String>> {
327 let mut images = Vec::new();
328
329 let options = Some(ListImagesOptions::<String> {
331 all: false,
332 ..Default::default()
333 });
334
335 match app_manager.docker.list_images(options).await {
336 Ok(image_list) => {
337 for image in image_list {
338 for tag in &image.repo_tags {
339 images.push(tag.clone());
340 }
341 }
342 },
343 Err(e) => {
344 eprintln!("Failed to list images: {}", e);
345 }
346 }
347
348 Json(images)
349}
350
351#[get("/events")]
352pub async fn stream_events(app_manager: &State<AppManager>) -> String {
353 let options = Some(EventsOptions::<String> {
357 ..Default::default()
358 });
359
360 let mut event_stream = app_manager.docker.events(options);
361
362 while let Some(event) = event_stream.next().await {
365 match event {
366 Ok(event) => {
367 println!("Event: {:?}", event);
368 },
370 Err(e) => {
371 eprintln!("Error receiving event: {}", e);
372 break;
373 }
374 }
375 }
376
377 "Event streaming would happen here".to_string()
378}
379
380#[get("/health")]
381pub fn health_check() -> String {
382 "App Manager is healthy".to_string()
383}
384
385#[get("/instances/<id>/logs")]
386pub async fn get_instance_logs(id: String, app_manager: &State<AppManager>) -> Result<String, String> {
387 let options = Some(bollard::container::LogsOptions::<String> {
388 stdout: true,
389 stderr: true,
390 follow: false,
391 timestamps: true,
392 tail: "100".to_string(),
393 ..Default::default()
394 });
395
396 match app_manager.docker.logs(&id, options).try_collect::<Vec<_>>().await {
397 Ok(logs) => {
398 let log_content = logs.iter()
399 .map(|chunk| {
400 match chunk {
401 bollard::container::LogOutput::StdOut { message: bytes } |
402 bollard::container::LogOutput::StdErr { message: bytes } => {
403 String::from_utf8_lossy(bytes).to_string()
404 },
405 bollard::container::LogOutput::StdIn { message: bytes } => {
406 String::from_utf8_lossy(bytes).to_string()
407 },
408 bollard::container::LogOutput::Console { message: bytes } => {
409 String::from_utf8_lossy(bytes).to_string()
410 }
411 }
412 })
413 .collect::<Vec<String>>()
414 .join("");
415 Ok(log_content)
416 },
417 Err(e) => Err(format!("Failed to fetch logs: {}", e))
418 }
419}
420
421#[get("/instances/<id>/stats")]
422pub async fn get_instance_stats(id: String, app_manager: &State<AppManager>) -> Result<Json<bollard::container::Stats>, String> {
423 match app_manager.docker.stats(&id, Some(bollard::container::StatsOptions {
424 stream: false,
425 one_shot: true,
426 })).try_next().await {
427 Ok(Some(stats)) => Ok(Json(stats)),
428 Ok(None) => Err("No stats available".to_string()),
429 Err(e) => Err(format!("Failed to get stats: {}", e))
430 }
431}
432
433#[put("/instances/<id>/pause")]
434pub async fn pause_instance(id: String, app_manager: &State<AppManager>) -> Result<String, String> {
435 match app_manager.docker.pause_container(&id).await {
436 Ok(_) => Ok(format!("Instance {} paused", id)),
437 Err(e) => Err(format!("Failed to pause instance: {}", e))
438 }
439}
440
441#[put("/instances/<id>/unpause")]
442pub async fn unpause_instance(id: String, app_manager: &State<AppManager>) -> Result<String, String> {
443 match app_manager.docker.unpause_container(&id).await {
444 Ok(_) => Ok(format!("Instance {} unpaused", id)),
445 Err(e) => Err(format!("Failed to unpause instance: {}", e))
446 }
447}
448
449#[get("/instances/<id>/inspect")]
450pub async fn inspect_instance(id: String, app_manager: &State<AppManager>) -> Result<Json<bollard::models::ContainerInspectResponse>, String> {
451 match app_manager.docker.inspect_container(&id, None).await {
452 Ok(info) => Ok(Json(info)),
453 Err(e) => Err(format!("Failed to inspect instance: {}", e))
454 }
455}
456
457#[derive(Debug, Clone, Serialize, Deserialize)]
460pub struct VolumeInfo {
461 name: String,
462 mountpoint: String,
463 labels: HashMap<String, String>,
464 created_at: String,
465}
466
467#[get("/volumes")]
468pub async fn list_volumes(app_manager: &State<AppManager>) -> Result<Json<Vec<VolumeInfo>>, String> {
469 match app_manager.docker.list_volumes::<String>(None).await {
470 Ok(volumes) => {
471 let volume_list = volumes.volumes.unwrap_or_default().into_iter()
472 .filter_map(|vol| {
473 let name = vol.name;
474 let mountpoint = vol.mountpoint;
475 let labels = vol.labels;
476 let created_at = vol.created_at.unwrap_or_default();
477
478 Some(VolumeInfo {
479 name,
480 mountpoint,
481 labels,
482 created_at,
483 })
484 })
485 .collect();
486
487 Ok(Json(volume_list))
488 },
489 Err(e) => Err(format!("Failed to list volumes: {}", e))
490 }
491}
492
493#[derive(Debug, Clone, Serialize, Deserialize)]
494pub struct VolumeCreateRequest {
495 name: String,
496 labels: Option<HashMap<String, String>>,
497}
498
499#[post("/volumes", format = "json", data = "<volume_req>")]
500pub async fn create_volume(volume_req: Json<VolumeCreateRequest>, app_manager: &State<AppManager>) -> Result<Json<VolumeInfo>, String> {
501 let options = bollard::volume::CreateVolumeOptions {
502 name: volume_req.name.clone(),
503 labels: volume_req.labels.clone().unwrap_or_default(),
504 ..Default::default()
505 };
506
507 match app_manager.docker.create_volume(options).await {
508 Ok(volume) => {
509 let volume_info = VolumeInfo {
510 name: volume.name,
511 mountpoint: volume.mountpoint,
512 labels: volume.labels,
513 created_at: volume.created_at.unwrap_or_default(),
514 };
515
516 Ok(Json(volume_info))
517 },
518 Err(e) => Err(format!("Failed to create volume: {}", e))
519 }
520}
521
522#[delete("/volumes/<name>")]
523pub async fn delete_volume(name: String, app_manager: &State<AppManager>) -> Result<String, String> {
524 match app_manager.docker.remove_volume(&name, None).await {
525 Ok(_) => Ok(format!("Volume {} deleted successfully", name)),
526 Err(e) => Err(format!("Failed to delete volume: {}", e))
527 }
528}
529
530#[derive(Debug, Clone, Serialize, Deserialize)]
533pub struct NetworkInfo {
534 id: String,
535 name: String,
536 driver: String,
537 scope: String,
538 containers: HashMap<String, NetworkContainerInfo>,
539}
540
541#[derive(Debug, Clone, Serialize, Deserialize)]
542pub struct NetworkContainerInfo {
543 name: String,
544 endpoint_id: String,
545 ipv4_address: String,
546}
547
548#[get("/networks")]
549pub async fn list_networks(app_manager: &State<AppManager>) -> Result<Json<Vec<NetworkInfo>>, String> {
550 match app_manager.docker.list_networks::<String>(None).await {
551 Ok(networks) => {
552 let network_list = networks.into_iter()
553 .filter_map(|net| {
554 let id = net.id?;
555 let name = net.name?;
556 let driver = net.driver?;
557 let scope = net.scope?;
558
559 let mut containers = HashMap::new();
560 if let Some(net_containers) = net.containers {
561 for (container_id, container_info) in net_containers {
562 if let (Some(name), Some(endpoint_id), Some(ipv4_address)) =
563 (container_info.name, container_info.endpoint_id, container_info.ipv4_address) {
564 containers.insert(container_id, NetworkContainerInfo {
565 name,
566 endpoint_id,
567 ipv4_address,
568 });
569 }
570 }
571 }
572
573 Some(NetworkInfo {
574 id,
575 name,
576 driver,
577 scope,
578 containers,
579 })
580 })
581 .collect();
582
583 Ok(Json(network_list))
584 },
585 Err(e) => Err(format!("Failed to list networks: {}", e))
586 }
587}
588
589#[derive(Debug, Clone, Serialize, Deserialize)]
590pub struct NetworkCreateRequest {
591 name: String,
592 driver: Option<String>,
593 labels: Option<HashMap<String, String>>,
594}
595
596#[post("/networks", format = "json", data = "<network_req>")]
597pub async fn create_network(network_req: Json<NetworkCreateRequest>, app_manager: &State<AppManager>) -> Result<Json<NetworkInfo>, String> {
598 let options = bollard::network::CreateNetworkOptions {
599 name: network_req.name.clone(),
600 driver: network_req.driver.clone().unwrap_or_default(),
601 labels: network_req.labels.clone().unwrap_or_default(),
602 ..Default::default()
603 };
604
605 match app_manager.docker.create_network(options).await {
606 Ok(response) => {
607 match app_manager.docker.inspect_network::<String>(response.id.as_str(), None).await {
609 Ok(network) => {
610 let mut containers = HashMap::new();
611 if let Some(net_containers) = network.containers {
612 for (container_id, container_info) in net_containers {
613 if let (Some(name), Some(endpoint_id), Some(ipv4_address)) =
614 (container_info.name, container_info.endpoint_id, container_info.ipv4_address) {
615 containers.insert(container_id, NetworkContainerInfo {
616 name,
617 endpoint_id,
618 ipv4_address,
619 });
620 }
621 }
622 }
623
624 let network_info = NetworkInfo {
625 id: network.id.unwrap_or_default(),
626 name: network.name.unwrap_or_default(),
627 driver: network.driver.unwrap_or_default(),
628 scope: network.scope.unwrap_or_default(),
629 containers,
630 };
631
632 Ok(Json(network_info))
633 },
634 Err(e) => Err(format!("Failed to inspect created network: {}", e))
635 }
636 },
637 Err(e) => Err(format!("Failed to create network: {}", e))
638 }
639}
640
641#[delete("/networks/<id>")]
642pub async fn delete_network(id: String, app_manager: &State<AppManager>) -> Result<String, String> {
643 match app_manager.docker.remove_network(&id).await {
644 Ok(_) => Ok(format!("Network {} deleted successfully", id)),
645 Err(e) => Err(format!("Failed to delete network: {}", e))
646 }
647}
648
649#[put("/instances/<id>/connect/<network_id>")]
650pub async fn connect_instance_to_network(id: String, network_id: String, app_manager: &State<AppManager>) -> Result<String, String> {
651 let options = bollard::network::ConnectNetworkOptions {
652 container: id.clone(),
653 ..Default::default()
654 };
655
656 match app_manager.docker.connect_network(&network_id, options).await {
657 Ok(_) => Ok(format!("Instance {} connected to network {}", id, network_id)),
658 Err(e) => Err(format!("Failed to connect instance to network: {}", e))
659 }
660}
661
662#[put("/instances/<id>/disconnect/<network_id>")]
663pub async fn disconnect_instance_from_network(id: String, network_id: String, app_manager: &State<AppManager>) -> Result<String, String> {
664 let options = bollard::network::DisconnectNetworkOptions {
665 container: id.clone(),
666 force: false,
667 };
668
669 match app_manager.docker.disconnect_network(&network_id, options).await {
670 Ok(_) => Ok(format!("Instance {} disconnected from network {}", id, network_id)),
671 Err(e) => Err(format!("Failed to disconnect instance from network: {}", e))
672 }
673}
674
675#[derive(Debug, Clone, Serialize, Deserialize)]
678pub struct AgentInfo {
679 id: String,
680 name: String,
681 version: String,
682 platform: String,
683 instance_count: usize,
684 status: String,
685 resources: SystemResources,
686}
687
688#[derive(Debug, Clone, Serialize, Deserialize)]
689pub struct SystemResources {
690 cpu_count: usize,
691 memory_total: u64,
692 memory_available: u64,
693 disk_total: u64,
694 disk_available: u64,
695}
696
697#[get("/agent/info")]
698pub async fn get_agent_info(app_manager: &State<AppManager>) -> Json<AgentInfo> {
699 let info = match app_manager.docker.info().await {
701 Ok(info) => info,
702 Err(e) => {
703 eprintln!("Failed to get Docker info: {}", e);
704 return Json(AgentInfo {
705 id: uuid::Uuid::new_v4().to_string(),
706 name: hostname::get().unwrap_or_default().to_string_lossy().to_string(),
707 version: "unknown".to_string(),
708 platform: "unknown".to_string(),
709 instance_count: app_manager.instances.lock().unwrap().len(),
710 status: "degraded".to_string(),
711 resources: SystemResources {
712 cpu_count: num_cpus::get(),
713 memory_total: 0,
714 memory_available: 0,
715 disk_total: 0,
716 disk_available: 0,
717 },
718 });
719 }
720 };
721
722 let memory_info = sys_info::mem_info().unwrap_or(sys_info::MemInfo {
724 total: 0,
725 free: 0,
726 avail: 0,
727 buffers: 0,
728 cached: 0,
729 swap_total: 0,
730 swap_free: 0,
731 });
732
733 let disk_info = sys_info::disk_info().unwrap_or(sys_info::DiskInfo {
734 total: 0,
735 free: 0,
736 });
737
738 Json(AgentInfo {
739 id: uuid::Uuid::new_v4().to_string(),
740 name: hostname::get().unwrap_or_default().to_string_lossy().to_string(),
741 version: info.server_version.unwrap_or_default(),
742 platform: format!("{} / {}",
743 info.operating_system.unwrap_or_default(),
744 info.architecture.unwrap_or_default()),
745 instance_count: app_manager.instances.lock().unwrap().len(),
746 status: "healthy".to_string(),
747 resources: SystemResources {
748 cpu_count: num_cpus::get(),
749 memory_total: memory_info.total * 1024,
750 memory_available: memory_info.avail * 1024,
751 disk_total: disk_info.total * 1024,
752 disk_available: disk_info.free * 1024,
753 },
754 })
755}