Fix stuck updates
Some checks failed
Build and Publish Docker Image / test (push) Failing after 1m8s
Build and Publish Docker Image / build-and-push (push) Has been skipped

This commit is contained in:
Andras Schmelczer 2026-06-21 13:52:42 +01:00
commit 30afb977ff
4 changed files with 395 additions and 110 deletions

View file

@ -25,6 +25,11 @@ pub struct ComposeUpdater {
pub struct UpdateReport {
pub files_found: usize,
pub updated_files: Vec<String>,
/// Human-readable failures collected during the run (one per failed service,
/// file, or path). Non-empty means the cycle was degraded but did not abort:
/// every other service and file was still processed and successful updates
/// were still written.
pub errors: Vec<String>,
}
impl ComposeUpdater {
@ -40,15 +45,29 @@ impl ComposeUpdater {
pub async fn update_all_compose_files(&self) -> Result<UpdateReport> {
let mut updated_files = Vec::new();
let mut files_found = 0;
let mut errors = Vec::new();
for compose_path in &self.config.compose_paths {
let compose_files = self.find_compose_files(compose_path)?;
let compose_files = match self.find_compose_files(compose_path) {
Ok(files) => files,
Err(e) => {
errors.push(format!("path {}: {:#}", compose_path.display(), e));
continue;
}
};
files_found += compose_files.len();
for file_path in compose_files {
let updated = self.update_compose_file(&file_path).await?;
if updated {
updated_files.push(file_path);
// A failure on one file (or one service within it) must not abort
// the rest of the run: collect it and keep going.
match self.update_compose_file(&file_path).await {
Ok((updated, file_errors)) => {
if updated {
updated_files.push(file_path);
}
errors.extend(file_errors);
}
Err(e) => errors.push(format!("file {}: {:#}", file_path, e)),
}
}
}
@ -56,6 +75,7 @@ impl ComposeUpdater {
Ok(UpdateReport {
files_found,
updated_files,
errors,
})
}
@ -63,11 +83,16 @@ impl ComposeUpdater {
self.parser.parse_file(file_path)
}
async fn update_compose_file(&self, file_path: &str) -> Result<bool> {
/// Updates every service in a single compose file. Returns whether the file
/// was modified along with a list of per-service failures. A transient
/// registry error on one service is recorded and skipped rather than
/// aborting the file, so unrelated services still get updated and written.
async fn update_compose_file(&self, file_path: &str) -> Result<(bool, Vec<String>)> {
info!("Processing compose file: {}", file_path);
let compose_file = self.parse_compose_file(file_path)?;
let mut updated = false;
let mut errors = Vec::new();
let mut new_content = compose_file.content.clone();
for service in &compose_file.services {
@ -77,15 +102,25 @@ impl ComposeUpdater {
}
match self.update_service_image(service).await {
Ok(Some(new_image)) => {
new_content =
self.replace_image_in_content(&new_content, service, &new_image)?;
updated = true;
info!(
"Updated {}: {} -> {}",
service.service_name, service.image_ref, new_image
);
}
Ok(Some(new_image)) => match self.replace_image_in_content(
&new_content,
service,
&new_image,
) {
Ok(replaced) => {
new_content = replaced;
updated = true;
info!(
"Updated {}: {} -> {}",
service.service_name, service.image_ref, new_image
);
}
Err(e) => {
let msg = format!("service {}: {:#}", service.service_name, e);
warn!("Failed to update {} in {}", msg, file_path);
errors.push(msg);
}
},
Ok(None) => {
debug!(
"No update needed for {}: {}",
@ -93,9 +128,9 @@ impl ComposeUpdater {
);
}
Err(e) => {
return Err(
e.context(format!("Failed to update service {}", service.service_name))
);
let msg = format!("service {}: {:#}", service.service_name, e);
warn!("Failed to update {} in {}", msg, file_path);
errors.push(msg);
}
}
}
@ -104,7 +139,7 @@ impl ComposeUpdater {
self.write_updated_content(file_path, new_content)?;
}
Ok(updated)
Ok((updated, errors))
}
fn write_updated_content(&self, file_path: &str, content: String) -> Result<()> {