use super::image::{image_exists, pull_image};
use super::profile::active_resource_names;
use super::progress::ProgressReporter;
use super::{DockerClient, DockerError, IMAGE_NAME_GHCR, IMAGE_TAG_DEFAULT};
use bollard::query_parameters::TagImageOptions;
use tracing::debug;
pub const PREVIOUS_TAG: &str = "previous";
#[derive(Debug, Clone, PartialEq)]
pub enum UpdateResult {
Success,
AlreadyLatest,
}
pub async fn tag_current_as_previous(client: &DockerClient) -> Result<(), DockerError> {
let names = active_resource_names();
let current_image = format!("{IMAGE_NAME_GHCR}:{}", names.image_tag);
let previous_image = format!("{IMAGE_NAME_GHCR}:{}", names.previous_image_tag);
debug!(
"Tagging current image {} as {}",
current_image, previous_image
);
if !image_exists(client, IMAGE_NAME_GHCR, &names.image_tag).await? {
debug!("Current image not found, skipping backup tag");
return Ok(());
}
let options = TagImageOptions {
repo: Some(IMAGE_NAME_GHCR.to_string()),
tag: Some(names.previous_image_tag),
};
client
.inner()
.tag_image(¤t_image, Some(options))
.await
.map_err(|e| {
DockerError::Container(format!("Failed to tag current image as previous: {e}"))
})?;
debug!("Successfully tagged current image as previous");
Ok(())
}
pub async fn has_previous_image(client: &DockerClient) -> Result<bool, DockerError> {
let names = active_resource_names();
image_exists(client, IMAGE_NAME_GHCR, &names.previous_image_tag).await
}
pub async fn update_image(
client: &DockerClient,
progress: &mut ProgressReporter,
) -> Result<UpdateResult, DockerError> {
progress.add_spinner("backup", "Backing up current image");
tag_current_as_previous(client).await?;
progress.finish("backup", "Current image backed up");
progress.add_spinner("pull", "Pulling latest image");
pull_image(client, Some(IMAGE_TAG_DEFAULT), progress).await?;
progress.finish("pull", "Latest image pulled");
Ok(UpdateResult::Success)
}
pub async fn rollback_image(client: &DockerClient) -> Result<(), DockerError> {
if !has_previous_image(client).await? {
return Err(DockerError::Container(
"No previous image available for rollback. Update at least once before using rollback."
.to_string(),
));
}
let names = active_resource_names();
let previous_image = format!("{IMAGE_NAME_GHCR}:{}", names.previous_image_tag);
let current_image = format!("{IMAGE_NAME_GHCR}:{}", names.image_tag);
debug!("Rolling back from {} to {}", current_image, previous_image);
let options = TagImageOptions {
repo: Some(IMAGE_NAME_GHCR.to_string()),
tag: Some(names.image_tag),
};
client
.inner()
.tag_image(&previous_image, Some(options))
.await
.map_err(|e| DockerError::Container(format!("Failed to rollback image: {e}")))?;
debug!("Successfully rolled back to previous image");
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn previous_tag_constant() {
assert_eq!(PREVIOUS_TAG, "previous");
}
#[test]
fn update_result_variants() {
assert_eq!(UpdateResult::Success, UpdateResult::Success);
assert_eq!(UpdateResult::AlreadyLatest, UpdateResult::AlreadyLatest);
assert_ne!(UpdateResult::Success, UpdateResult::AlreadyLatest);
}
}