opencode_cloud_core/docker/
update.rs1use super::image::{image_exists, pull_image};
7use super::progress::ProgressReporter;
8use super::{DockerClient, DockerError, IMAGE_NAME_GHCR, IMAGE_TAG_DEFAULT};
9use bollard::image::TagImageOptions;
10use tracing::debug;
11
12pub const PREVIOUS_TAG: &str = "previous";
14
15#[derive(Debug, Clone, PartialEq)]
17pub enum UpdateResult {
18 Success,
20 AlreadyLatest,
22}
23
24pub async fn tag_current_as_previous(client: &DockerClient) -> Result<(), DockerError> {
32 let current_image = format!("{IMAGE_NAME_GHCR}:{IMAGE_TAG_DEFAULT}");
33 let previous_image = format!("{IMAGE_NAME_GHCR}:{PREVIOUS_TAG}");
34
35 debug!(
36 "Tagging current image {} as {}",
37 current_image, previous_image
38 );
39
40 if !image_exists(client, IMAGE_NAME_GHCR, IMAGE_TAG_DEFAULT).await? {
42 debug!("Current image not found, skipping backup tag");
43 return Ok(());
44 }
45
46 let options = TagImageOptions {
48 repo: IMAGE_NAME_GHCR,
49 tag: PREVIOUS_TAG,
50 };
51
52 client
53 .inner()
54 .tag_image(¤t_image, Some(options))
55 .await
56 .map_err(|e| {
57 DockerError::Container(format!("Failed to tag current image as previous: {e}"))
58 })?;
59
60 debug!("Successfully tagged current image as previous");
61 Ok(())
62}
63
64pub async fn has_previous_image(client: &DockerClient) -> Result<bool, DockerError> {
71 image_exists(client, IMAGE_NAME_GHCR, PREVIOUS_TAG).await
72}
73
74pub async fn update_image(
86 client: &DockerClient,
87 progress: &mut ProgressReporter,
88) -> Result<UpdateResult, DockerError> {
89 progress.add_spinner("backup", "Backing up current image");
91 tag_current_as_previous(client).await?;
92 progress.finish("backup", "Current image backed up");
93
94 progress.add_spinner("pull", "Pulling latest image");
96 pull_image(client, Some(IMAGE_TAG_DEFAULT), progress).await?;
97 progress.finish("pull", "Latest image pulled");
98
99 Ok(UpdateResult::Success)
100}
101
102pub async fn rollback_image(client: &DockerClient) -> Result<(), DockerError> {
112 if !has_previous_image(client).await? {
114 return Err(DockerError::Container(
115 "No previous image available for rollback. Update at least once before using rollback."
116 .to_string(),
117 ));
118 }
119
120 let previous_image = format!("{IMAGE_NAME_GHCR}:{PREVIOUS_TAG}");
121 let current_image = format!("{IMAGE_NAME_GHCR}:{IMAGE_TAG_DEFAULT}");
122
123 debug!("Rolling back from {} to {}", current_image, previous_image);
124
125 let options = TagImageOptions {
127 repo: IMAGE_NAME_GHCR,
128 tag: IMAGE_TAG_DEFAULT,
129 };
130
131 client
132 .inner()
133 .tag_image(&previous_image, Some(options))
134 .await
135 .map_err(|e| DockerError::Container(format!("Failed to rollback image: {e}")))?;
136
137 debug!("Successfully rolled back to previous image");
138 Ok(())
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144
145 #[test]
146 fn previous_tag_constant() {
147 assert_eq!(PREVIOUS_TAG, "previous");
148 }
149
150 #[test]
151 fn update_result_variants() {
152 assert_eq!(UpdateResult::Success, UpdateResult::Success);
153 assert_eq!(UpdateResult::AlreadyLatest, UpdateResult::AlreadyLatest);
154 assert_ne!(UpdateResult::Success, UpdateResult::AlreadyLatest);
155 }
156}