anvilsign in

collin/anvil

1//! Docker plumbing shared by the CI runner and the agent-session supervisor.
2//!
3//! Both create containers from the same runner image
4//! ([`anvil_core::config::DEFAULT_RUNNER_IMAGE`]) against the same daemon, so
5//! the connect/pull dance lives here rather than being written twice.
6
7use bollard::{
8 Docker,
9 image::CreateImageOptions,
10};
11use futures_util::StreamExt;
12
13/// Connect to the daemon over the local socket.
14pub fn connect() -> Result<Docker, String> {
15 Docker::connect_with_socket_defaults()
16 .map_err(|e| format!("docker unavailable (is the socket mounted?): {e}"))
17}
18
19/// Make sure `image` is present locally, pulling it if it is not.
20///
21/// A failed pull is only fatal when the image is *also* absent locally. anvil's
22/// own runner image is built by `deploy/runner/build.sh` straight into the
23/// host's image store and exists in no registry, so an unconditional pull —
24/// which is what this used to be — fails for the one image most jobs now use.
25pub async fn ensure_image(docker: &Docker, image: &str) -> Result<(), String> {
26 // Split name:tag so we don't accidentally pull every tag. A ':' that has a
27 // '/' after it is a registry port, not a tag.
28 let (from_image, tag) = match image.rsplit_once(':') {
29 Some((name, tag)) if !tag.contains('/') => (name.to_string(), tag.to_string()),
30 _ => (image.to_string(), "latest".to_string()),
31 };
32
33 let mut pull = docker.create_image(
34 Some(CreateImageOptions {
35 from_image,
36 tag,
37 ..Default::default()
38 }),
39 None,
40 None,
41 );
42 let mut pull_error = None;
43 while let Some(item) = pull.next().await {
44 if let Err(e) = item {
45 pull_error = Some(e.to_string());
46 break;
47 }
48 }
49
50 let Some(pull_error) = pull_error else {
51 return Ok(());
52 };
53
54 // The pull failed. That is fine if the image is already here — the local
55 // build case — and fatal otherwise.
56 match docker.inspect_image(image).await {
57 Ok(_) => {
58 tracing::debug!("pull of {image} failed ({pull_error}); using the local image");
59 Ok(())
60 }
61 Err(_) => Err(format!(
62 "image {image} is not available locally and could not be pulled: {pull_error}"
63 )),
64 }
65}