diff --git a/packages/runtime-core/src/provider/registry_credentials.rs b/packages/runtime-core/src/provider/registry_credentials.rs index 1734053b3..b4442fdbe 100644 --- a/packages/runtime-core/src/provider/registry_credentials.rs +++ b/packages/runtime-core/src/provider/registry_credentials.rs @@ -330,7 +330,7 @@ fn run_helper( "The Docker credential helper failed; its output was discarded.", )); } - if written.is_some_and(|result| result.is_err()) { + if written.as_ref().is_some_and(helper_write_failed) { return Err(error( "Cannot send the server address to the Docker credential helper.", )); @@ -415,6 +415,14 @@ pub(super) fn lookup( entry.map_or(Ok(None), file_credential) } +/// Like Go's `os/exec`, which Docker uses to run helpers, a helper that exits without +/// reading its input is not a failure: its exit status and reply decide the outcome. +fn helper_write_failed(result: &std::io::Result<()>) -> bool { + result + .as_ref() + .is_err_and(|error| error.kind() != std::io::ErrorKind::BrokenPipe) +} + #[cfg(test)] pub(super) mod tests { use super::*; @@ -639,4 +647,19 @@ pub(super) mod tests { .contains("PRIVATE_CANARY") ); } + + #[test] + fn a_helper_that_closes_its_input_early_is_judged_by_its_reply() { + use std::io::{Error, ErrorKind}; + assert!(!helper_write_failed(&Ok(()))); + assert!(!helper_write_failed(&Err(Error::from( + ErrorKind::BrokenPipe + )))); + assert!(helper_write_failed(&Err(Error::from( + ErrorKind::PermissionDenied + )))); + assert!(helper_write_failed(&Err(Error::from( + ErrorKind::Interrupted + )))); + } }