1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
//! Deciding which image a service runs, and getting it there.
//!
//! Split out of `mod.rs` to keep that file within the source line limit.
use crate::compose::types::{ComposeFile, Service};
use crate::error::Result;
use super::Engine;
impl Engine {
/// The image tag a service resolves to: its explicit `image:` when set,
/// otherwise the tag its build produces (`build.tags[0]`, else the
/// project-scoped `{project}-{service}:latest`).
///
/// This is the name `up` checks for presence and the name `down --rmi local`
/// removes, so both must agree on it — they used to compute it separately.
pub(super) fn service_image_tag(&self, name: &str, service: &Service) -> String {
match &service.image {
Some(image) => image.clone(),
None => crate::engine::build::primary_build_tag(
&self.project,
name,
None,
service.build.as_ref().map(|b| b.tags()).unwrap_or(&[]),
),
}
}
/// Make the service's image available before its containers are created:
/// build it, pull it, or leave the local one alone.
pub(super) async fn acquire_service_image(
&self,
name: &str,
service: &Service,
file: &ComposeFile,
) -> Result<()> {
// `up --pull <policy>` overrides the per-service `pull_policy`; `--no-build`
// suppresses building even for services with a `build:` section (they fall
// back to pulling/using an existing image).
let policy = self
.pull_policy_override
.as_deref()
.or(service.pull_policy.as_deref())
.unwrap_or("missing");
// Build on `up` only when the service's image is not already there, which
// is what docker compose does: `up` converges on the declared state and
// `--build` is the flag that forces a rebuild.
//
// Building unconditionally was worse than redundant. The rebuild runs
// *with* the cache, so it can resolve to an older layer chain and retag
// the image backwards — silently undoing a `podup build --no-cache` that
// just ran. `build --no-cache && up -d`, the ordinary deploy shape, would
// start the previous image. It also made `--build` look like a no-op,
// since the default already always built.
//
// `--build` is handled before this by an explicit `build_all`, so a forced
// rebuild has already happened and the image is present by the time we get
// here.
let needs_build = if service.build.is_some() && !self.no_build {
!self
.image_present(&self.service_image_tag(name, service))
.await
} else {
false
};
match (needs_build, policy) {
(true, _) => {
self.build_service(name, service, file, &crate::engine::BuildOptions::default())
.await?
}
// A service with a `build:` whose image is already present needs no
// pull either — the local tag is the declared state.
(false, _) if service.build.is_some() => {}
(false, "never") => {}
(false, _) => self.pull_image(service).await?,
}
Ok(())
}
}