fix(orchestrator): install_fresh skips pull when the image is local
Reconcile recreates route through install_fresh, which pulled unconditionally: with git.tx1138.com dead, every boot reconcile of archy-btcpay-db/archy-nbxplorer hard-failed on 'pulling ...' even though both image:tags sat in local storage the whole time (live-testing report 2026-07-10). Check image_exists first and only pull when the ref is truly absent — the same semantics as ensure_resolved_source_available and the generated quadlets' Pull=never. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
cfdf2da9a5
commit
3a5c5db187
@ -2050,11 +2050,31 @@ impl ProdContainerOrchestrator {
|
||||
lm.manifest.app.id
|
||||
);
|
||||
}
|
||||
// Reconcile recreates route through here too: an unreachable
|
||||
// registry must not brick an app whose exact image:tag is
|
||||
// already in local storage (boot reconcile of archy-btcpay-db
|
||||
// /archy-nbxplorer with git.tx1138.com dead, 2026-07-10).
|
||||
// Same exists-first semantics as
|
||||
// ensure_resolved_source_available and the quadlets'
|
||||
// Pull=never.
|
||||
let exists = match self.runtime.image_exists(&image).await {
|
||||
Ok(exists) => exists,
|
||||
Err(err) => {
|
||||
tracing::warn!(
|
||||
image = %image,
|
||||
error = %err,
|
||||
"image existence check failed; pulling image instead"
|
||||
);
|
||||
false
|
||||
}
|
||||
};
|
||||
if !exists {
|
||||
self.runtime
|
||||
.pull_image(&image, image_signature.as_deref())
|
||||
.await
|
||||
.with_context(|| format!("pulling {image}"))?;
|
||||
}
|
||||
}
|
||||
ResolvedSource::Build(mut bcfg) => {
|
||||
// Resolve a relative context: against the manifest's own directory.
|
||||
let ctx_path = Path::new(&bcfg.context);
|
||||
@ -4195,6 +4215,8 @@ mod tests {
|
||||
StdMutex<HashMap<String, Vec<archipelago_container::manifest::SecretEnvRef>>>,
|
||||
/// If set, the next `build_image` call fails with this message.
|
||||
fail_build: StdMutex<Option<String>>,
|
||||
/// If set, every `pull_image` call fails with this message.
|
||||
fail_pull: StdMutex<Option<String>>,
|
||||
/// If set, `image_exists` fails for this image reference.
|
||||
fail_image_exists: StdMutex<HashMap<String, String>>,
|
||||
/// If set, `start_container` for this container fails with this message.
|
||||
@ -4248,6 +4270,9 @@ mod tests {
|
||||
impl ContainerRuntimeTrait for MockRuntime {
|
||||
async fn pull_image(&self, image: &str, _sig: Option<&str>) -> Result<()> {
|
||||
self.record(format!("pull_image:{image}"));
|
||||
if let Some(msg) = self.fail_pull.lock().unwrap().clone() {
|
||||
return Err(anyhow::anyhow!("{msg}"));
|
||||
}
|
||||
self.mark_image_present(image);
|
||||
Ok(())
|
||||
}
|
||||
@ -4691,6 +4716,44 @@ app:
|
||||
assert!(!calls.iter().any(|c| c.starts_with("build_image:")));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn install_fresh_skips_pull_when_image_local() {
|
||||
// An unreachable registry must not brick a reconcile recreate whose
|
||||
// exact image:tag is already in local storage (archy-btcpay-db /
|
||||
// archy-nbxplorer boot reconcile with git.tx1138.com dead,
|
||||
// 2026-07-10).
|
||||
let rt = Arc::new(MockRuntime::default());
|
||||
rt.mark_image_present("docker.io/bitcoin/knots:28");
|
||||
*rt.fail_pull.lock().unwrap() = Some("registry unreachable".into());
|
||||
let orch = orch_with(rt.clone()).await;
|
||||
orch.insert_manifest_for_test(
|
||||
pull_manifest("bitcoin-knots", "docker.io/bitcoin/knots:28"),
|
||||
PathBuf::from("/tmp/fixtures/bitcoin-knots"),
|
||||
)
|
||||
.await;
|
||||
|
||||
let name = orch.install("bitcoin-knots").await.unwrap();
|
||||
assert_eq!(name, "bitcoin-knots");
|
||||
let calls = rt.calls();
|
||||
assert!(!calls.iter().any(|c| c.starts_with("pull_image:")));
|
||||
assert!(calls.iter().any(|c| c == "start_container:bitcoin-knots"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn install_fresh_still_fails_when_image_truly_absent() {
|
||||
let rt = Arc::new(MockRuntime::default());
|
||||
*rt.fail_pull.lock().unwrap() = Some("registry unreachable".into());
|
||||
let orch = orch_with(rt.clone()).await;
|
||||
orch.insert_manifest_for_test(
|
||||
pull_manifest("bitcoin-knots", "docker.io/bitcoin/knots:28"),
|
||||
PathBuf::from("/tmp/fixtures/bitcoin-knots"),
|
||||
)
|
||||
.await;
|
||||
|
||||
let err = orch.install("bitcoin-knots").await.unwrap_err();
|
||||
assert!(err.to_string().contains("pulling"), "err: {err}");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn knows_app_reflects_loaded_manifests() {
|
||||
// The RPC install gate routes any app with a known manifest through
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user