opencode_cloud_core/docker/
volume.rs1use super::{DockerClient, DockerError};
7use bollard::volume::CreateVolumeOptions;
8use std::collections::HashMap;
9use tracing::debug;
10
11pub const VOLUME_SESSION: &str = "opencode-cloud-session";
13
14pub const VOLUME_PROJECTS: &str = "opencode-cloud-projects";
16
17pub const VOLUME_CONFIG: &str = "opencode-cloud-config";
19
20pub const VOLUME_NAMES: [&str; 3] = [VOLUME_SESSION, VOLUME_PROJECTS, VOLUME_CONFIG];
22
23pub const MOUNT_SESSION: &str = "/home/opencode/.opencode";
25
26pub const MOUNT_PROJECTS: &str = "/workspace";
28
29pub const MOUNT_CONFIG: &str = "/home/opencode/.config";
31
32pub async fn ensure_volumes_exist(client: &DockerClient) -> Result<(), DockerError> {
37 debug!("Ensuring all required volumes exist");
38
39 for volume_name in VOLUME_NAMES {
40 ensure_volume_exists(client, volume_name).await?;
41 }
42
43 debug!("All volumes verified/created");
44 Ok(())
45}
46
47async fn ensure_volume_exists(client: &DockerClient, name: &str) -> Result<(), DockerError> {
49 debug!("Checking volume: {}", name);
50
51 let options = CreateVolumeOptions {
53 name,
54 driver: "local",
55 driver_opts: HashMap::new(),
56 labels: HashMap::from([("managed-by", "opencode-cloud")]),
57 };
58
59 client
61 .inner()
62 .create_volume(options)
63 .await
64 .map_err(|e| DockerError::Volume(format!("Failed to create volume {name}: {e}")))?;
65
66 debug!("Volume {} ready", name);
67 Ok(())
68}
69
70pub async fn volume_exists(client: &DockerClient, name: &str) -> Result<bool, DockerError> {
72 debug!("Checking if volume exists: {}", name);
73
74 match client.inner().inspect_volume(name).await {
75 Ok(_) => Ok(true),
76 Err(bollard::errors::Error::DockerResponseServerError {
77 status_code: 404, ..
78 }) => Ok(false),
79 Err(e) => Err(DockerError::Volume(format!(
80 "Failed to inspect volume {name}: {e}"
81 ))),
82 }
83}
84
85pub async fn remove_volume(client: &DockerClient, name: &str) -> Result<(), DockerError> {
90 debug!("Removing volume: {}", name);
91
92 client
93 .inner()
94 .remove_volume(name, None)
95 .await
96 .map_err(|e| DockerError::Volume(format!("Failed to remove volume {name}: {e}")))?;
97
98 debug!("Volume {} removed", name);
99 Ok(())
100}
101
102pub async fn remove_all_volumes(client: &DockerClient) -> Result<(), DockerError> {
106 debug!("Removing all opencode-cloud volumes");
107
108 for volume_name in VOLUME_NAMES {
109 if volume_exists(client, volume_name).await? {
111 remove_volume(client, volume_name).await?;
112 }
113 }
114
115 debug!("All volumes removed");
116 Ok(())
117}
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122
123 #[test]
124 fn volume_constants_are_correct() {
125 assert_eq!(VOLUME_SESSION, "opencode-cloud-session");
126 assert_eq!(VOLUME_PROJECTS, "opencode-cloud-projects");
127 assert_eq!(VOLUME_CONFIG, "opencode-cloud-config");
128 }
129
130 #[test]
131 fn volume_names_array_has_all_volumes() {
132 assert_eq!(VOLUME_NAMES.len(), 3);
133 assert!(VOLUME_NAMES.contains(&VOLUME_SESSION));
134 assert!(VOLUME_NAMES.contains(&VOLUME_PROJECTS));
135 assert!(VOLUME_NAMES.contains(&VOLUME_CONFIG));
136 }
137
138 #[test]
139 fn mount_points_are_correct() {
140 assert_eq!(MOUNT_SESSION, "/home/opencode/.opencode");
141 assert_eq!(MOUNT_PROJECTS, "/workspace");
142 assert_eq!(MOUNT_CONFIG, "/home/opencode/.config");
143 }
144}