Skip to content

Commit 04e9e6b

Browse files
committed
add SandboxBuilder image override
1 parent 7a4ef73 commit 04e9e6b

File tree

4 files changed

+66
-5
lines changed

4 files changed

+66
-5
lines changed

src/cmd/mod.rs

+8
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,14 @@ pub enum CommandError {
100100
#[error("invalid output of `docker inspect`: {0}")]
101101
InvalidDockerInspectOutput(#[source] serde_json::Error),
102102

103+
/// The data received from the `docker manifest inspect` command is not valid.
104+
#[error("invalid output from `docker manifest inspect`: {0}")]
105+
InvalidDockerManifestInspectOutput(#[source] serde_json::Error),
106+
107+
/// The remote image is larger than the specified size limit.
108+
#[error("sandbox image is too large: {0}")]
109+
SandboxImageTooLarge(usize),
110+
103111
/// An I/O error occured while executing the command.
104112
#[error(transparent)]
105113
IO(#[from] std::io::Error),

src/cmd/sandbox.rs

+55-3
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,22 @@ pub struct SandboxImage {
1212
name: String,
1313
}
1414

15+
#[derive(serde::Deserialize)]
16+
struct DockerManifest {
17+
config: DockerManifestConfig,
18+
layers: Vec<DockerManifestLayer>,
19+
}
20+
21+
#[derive(serde::Deserialize)]
22+
struct DockerManifestConfig {
23+
digest: String,
24+
}
25+
26+
#[derive(serde::Deserialize)]
27+
struct DockerManifestLayer {
28+
size: usize,
29+
}
30+
1531
impl SandboxImage {
1632
/// Load a local image present in the host machine.
1733
///
@@ -27,11 +43,33 @@ impl SandboxImage {
2743
///
2844
/// This will access the network to download the image from the registry. If pulling fails an
2945
/// error will be returned instead.
30-
pub fn remote(name: &str) -> Result<Self, CommandError> {
46+
pub fn remote(name: &str, size_limit: Option<usize>) -> Result<Self, CommandError> {
3147
let mut image = SandboxImage { name: name.into() };
48+
let digest = if let Some(size_limit) = size_limit {
49+
let out = Command::new_workspaceless("docker")
50+
.args(&["manifest", "inspect", name])
51+
.run_capture()?
52+
.stdout_lines()
53+
.join("\n");
54+
let m: DockerManifest = serde_json::from_str(&out)
55+
.map_err(CommandError::InvalidDockerManifestInspectOutput)?;
56+
let size = m.layers.iter().fold(0, |acc, l| acc + l.size);
57+
if size > size_limit {
58+
return Err(CommandError::SandboxImageTooLarge(size));
59+
}
60+
Some(m.config.digest)
61+
} else {
62+
None
63+
};
3264
info!("pulling image {} from Docker Hub", name);
3365
Command::new_workspaceless("docker")
34-
.args(&["pull", &name])
66+
.args(&[
67+
"pull",
68+
&digest.map_or(name.to_string(), |digest| {
69+
let name = name.split(':').next().unwrap();
70+
format!("{}@{}", name, digest)
71+
}),
72+
])
3573
.run()
3674
.map_err(|e| CommandError::SandboxImagePullFailed(Box::new(e)))?;
3775
if let Some(name_with_hash) = image.get_name_with_hash() {
@@ -146,6 +184,7 @@ pub struct SandboxBuilder {
146184
user: Option<String>,
147185
cmd: Vec<String>,
148186
enable_networking: bool,
187+
image: Option<String>,
149188
}
150189

151190
impl SandboxBuilder {
@@ -160,6 +199,7 @@ impl SandboxBuilder {
160199
user: None,
161200
cmd: Vec::new(),
162201
enable_networking: true,
202+
image: None,
163203
}
164204
}
165205

@@ -203,6 +243,14 @@ impl SandboxBuilder {
203243
self
204244
}
205245

246+
/// Override the image used for this sandbox.
247+
///
248+
/// By default rustwide will use the image configured with [`WorkspaceBuilder::sandbox_image`].
249+
pub fn image(mut self, image: SandboxImage) -> Self {
250+
self.image = Some(image.name);
251+
self
252+
}
253+
206254
pub(super) fn env<S1: Into<String>, S2: Into<String>>(mut self, key: S1, value: S2) -> Self {
207255
self.env.push((key.into(), value.into()));
208256
self
@@ -274,7 +322,11 @@ impl SandboxBuilder {
274322
args.push("--isolation=process".into());
275323
}
276324

277-
args.push(workspace.sandbox_image().name.clone());
325+
if let Some(image) = self.image {
326+
args.push(image);
327+
} else {
328+
args.push(workspace.sandbox_image().name.clone());
329+
}
278330

279331
for arg in self.cmd {
280332
args.push(arg);

src/crates/mod.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,8 @@ impl Crate {
7373
}
7474
}
7575

76-
pub(crate) fn copy_source_to(&self, workspace: &Workspace, dest: &Path) -> Result<(), Error> {
76+
/// Copy this crate's source to `dest`. If `dest` already exists, it will be replaced.
77+
pub fn copy_source_to(&self, workspace: &Workspace, dest: &Path) -> Result<(), Error> {
7778
if dest.exists() {
7879
info!(
7980
"crate source directory {} already exists, cleaning it up",

src/workspace.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ impl WorkspaceBuilder {
146146
let sandbox_image = if let Some(img) = self.sandbox_image {
147147
img
148148
} else {
149-
SandboxImage::remote(DEFAULT_SANDBOX_IMAGE)?
149+
SandboxImage::remote(DEFAULT_SANDBOX_IMAGE, None)?
150150
};
151151

152152
let mut agent = attohttpc::Session::new();

0 commit comments

Comments
 (0)