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-data";
13
14pub const VOLUME_STATE: &str = "opencode-state";
16
17pub const VOLUME_CACHE: &str = "opencode-cache";
19
20pub const VOLUME_PROJECTS: &str = "opencode-workspace";
22
23pub const VOLUME_CONFIG: &str = "opencode-config";
25
26pub const VOLUME_NAMES: [&str; 5] = [
28 VOLUME_SESSION,
29 VOLUME_STATE,
30 VOLUME_CACHE,
31 VOLUME_PROJECTS,
32 VOLUME_CONFIG,
33];
34
35pub const MOUNT_SESSION: &str = "/home/opencode/.local/share/opencode";
37
38pub const MOUNT_STATE: &str = "/home/opencode/.local/state/opencode";
40
41pub const MOUNT_CACHE: &str = "/home/opencode/.cache/opencode";
43
44pub const MOUNT_PROJECTS: &str = "/home/opencode/workspace";
46
47pub const MOUNT_CONFIG: &str = "/home/opencode/.config/opencode";
49
50pub async fn ensure_volumes_exist(client: &DockerClient) -> Result<(), DockerError> {
55 debug!("Ensuring all required volumes exist");
56
57 for volume_name in VOLUME_NAMES {
58 ensure_volume_exists(client, volume_name).await?;
59 }
60
61 debug!("All volumes verified/created");
62 Ok(())
63}
64
65async fn ensure_volume_exists(client: &DockerClient, name: &str) -> Result<(), DockerError> {
67 debug!("Checking volume: {}", name);
68
69 let options = CreateVolumeOptions {
71 name,
72 driver: "local",
73 driver_opts: HashMap::new(),
74 labels: HashMap::from([("managed-by", "opencode-cloud")]),
75 };
76
77 client
79 .inner()
80 .create_volume(options)
81 .await
82 .map_err(|e| DockerError::Volume(format!("Failed to create volume {name}: {e}")))?;
83
84 debug!("Volume {} ready", name);
85 Ok(())
86}
87
88pub async fn volume_exists(client: &DockerClient, name: &str) -> Result<bool, DockerError> {
90 debug!("Checking if volume exists: {}", name);
91
92 match client.inner().inspect_volume(name).await {
93 Ok(_) => Ok(true),
94 Err(bollard::errors::Error::DockerResponseServerError {
95 status_code: 404, ..
96 }) => Ok(false),
97 Err(e) => Err(DockerError::Volume(format!(
98 "Failed to inspect volume {name}: {e}"
99 ))),
100 }
101}
102
103pub async fn remove_volume(client: &DockerClient, name: &str) -> Result<(), DockerError> {
108 debug!("Removing volume: {}", name);
109
110 client
111 .inner()
112 .remove_volume(name, None)
113 .await
114 .map_err(|e| DockerError::Volume(format!("Failed to remove volume {name}: {e}")))?;
115
116 debug!("Volume {} removed", name);
117 Ok(())
118}
119
120pub async fn remove_all_volumes(client: &DockerClient) -> Result<(), DockerError> {
124 debug!("Removing all opencode-cloud volumes");
125
126 for volume_name in VOLUME_NAMES {
127 if volume_exists(client, volume_name).await? {
129 remove_volume(client, volume_name).await?;
130 }
131 }
132
133 debug!("All volumes removed");
134 Ok(())
135}
136
137#[cfg(test)]
138mod tests {
139 use super::*;
140
141 #[test]
142 fn volume_constants_are_correct() {
143 assert_eq!(VOLUME_SESSION, "opencode-data");
144 assert_eq!(VOLUME_STATE, "opencode-state");
145 assert_eq!(VOLUME_CACHE, "opencode-cache");
146 assert_eq!(VOLUME_PROJECTS, "opencode-workspace");
147 assert_eq!(VOLUME_CONFIG, "opencode-config");
148 }
149
150 #[test]
151 fn volume_names_array_has_all_volumes() {
152 assert_eq!(VOLUME_NAMES.len(), 5);
153 assert!(VOLUME_NAMES.contains(&VOLUME_SESSION));
154 assert!(VOLUME_NAMES.contains(&VOLUME_STATE));
155 assert!(VOLUME_NAMES.contains(&VOLUME_CACHE));
156 assert!(VOLUME_NAMES.contains(&VOLUME_PROJECTS));
157 assert!(VOLUME_NAMES.contains(&VOLUME_CONFIG));
158 }
159
160 #[test]
161 fn mount_points_are_correct() {
162 assert_eq!(MOUNT_SESSION, "/home/opencode/.local/share/opencode");
163 assert_eq!(MOUNT_STATE, "/home/opencode/.local/state/opencode");
164 assert_eq!(MOUNT_CACHE, "/home/opencode/.cache/opencode");
165 assert_eq!(MOUNT_PROJECTS, "/home/opencode/workspace");
166 assert_eq!(MOUNT_CONFIG, "/home/opencode/.config/opencode");
167 }
168}