Skip to content

Commit 5896ed1

Browse files
authored
feat(rootless): help detection with execv check "is current builder rootless?" (#1380)
I recently encountered #1098 and propose an iteration on the fix in #890 This patch adds a last minute check that looks up the current builder endpoint. This may be seen as a costly operation however, as mentioned in #889 Here's the output of `docker builder inspect` on my rootless install: ``` Name: rootless Driver: docker Last Activity: 2023-12-03 02:04:14 +0000 UTC Nodes: Name: rootless Endpoint: rootless # <= THIS HERE Status: running Buildkit: v0.11.7+d3e6c1360f6e Platforms: linux/amd64, linux/amd64/v2, linux/amd64/v3, linux/amd64/v4, linux/386 Labels: org.mobyproject.buildkit.worker.moby.host-gateway-ip: 172.17.0.1 ```
2 parents bfc59f0 + edf7422 commit 5896ed1

File tree

4 files changed

+75
-44
lines changed

4 files changed

+75
-44
lines changed

src/docker/engine.rs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ pub struct Engine {
6666
pub arch: Option<Architecture>,
6767
pub os: Option<ContainerOs>,
6868
pub is_remote: bool,
69+
pub is_rootless: bool,
6970
}
7071

7172
impl Engine {
@@ -94,6 +95,7 @@ impl Engine {
9495
None => Self::in_docker(msg_info)?,
9596
};
9697
let (kind, arch, os) = get_engine_info(&path, msg_info)?;
98+
let is_rootless = is_rootless(kind).unwrap_or_else(|| is_docker_rootless(&path, msg_info));
9799
let is_remote = is_remote.unwrap_or_else(Self::is_remote);
98100
Ok(Engine {
99101
path,
@@ -102,6 +104,7 @@ impl Engine {
102104
arch,
103105
os,
104106
is_remote,
107+
is_rootless,
105108
})
106109
}
107110

@@ -143,6 +146,69 @@ impl Engine {
143146
}
144147
}
145148

149+
fn is_rootless(kind: EngineType) -> Option<bool> {
150+
env::var("CROSS_ROOTLESS_CONTAINER_ENGINE")
151+
.ok()
152+
.and_then(|s| match s.as_ref() {
153+
"auto" => None,
154+
b => Some(bool_from_envvar(b)),
155+
})
156+
.or_else(|| (!kind.is_docker()).then_some(true))
157+
}
158+
159+
#[must_use]
160+
fn is_docker_rootless(ce: &Path, msg_info: &mut MessageInfo) -> bool {
161+
let mut cmd = Command::new(ce);
162+
cmd.args(["info", "-f", "{{.SecurityOptions}}"])
163+
.run_and_get_output(msg_info)
164+
.ok()
165+
.and_then(|cmd| cmd.stdout().ok())
166+
.map(|out| {
167+
out.to_lowercase()
168+
.replace(['[', ' ', ']'], ",")
169+
.contains(",name=rootless,")
170+
})
171+
.unwrap_or_default()
172+
}
173+
174+
#[test]
175+
fn various_is_rootless_configs() {
176+
let var = "CROSS_ROOTLESS_CONTAINER_ENGINE";
177+
let old = env::var(var);
178+
env::remove_var(var);
179+
180+
assert!(!is_rootless(EngineType::Docker).unwrap_or(false));
181+
assert!(is_rootless(EngineType::Docker).unwrap_or(true));
182+
183+
assert_eq!(is_rootless(EngineType::Docker), None);
184+
assert_eq!(is_rootless(EngineType::Podman), Some(true));
185+
assert_eq!(is_rootless(EngineType::PodmanRemote), Some(true));
186+
assert_eq!(is_rootless(EngineType::Other), Some(true));
187+
188+
env::set_var(var, "0");
189+
assert_eq!(is_rootless(EngineType::Docker), Some(false));
190+
assert_eq!(is_rootless(EngineType::Podman), Some(false));
191+
assert_eq!(is_rootless(EngineType::PodmanRemote), Some(false));
192+
assert_eq!(is_rootless(EngineType::Other), Some(false));
193+
194+
env::set_var(var, "1");
195+
assert_eq!(is_rootless(EngineType::Docker), Some(true));
196+
assert_eq!(is_rootless(EngineType::Podman), Some(true));
197+
assert_eq!(is_rootless(EngineType::PodmanRemote), Some(true));
198+
assert_eq!(is_rootless(EngineType::Other), Some(true));
199+
200+
env::set_var(var, "auto");
201+
assert_eq!(is_rootless(EngineType::Docker), None);
202+
assert_eq!(is_rootless(EngineType::Podman), Some(true));
203+
assert_eq!(is_rootless(EngineType::PodmanRemote), Some(true));
204+
assert_eq!(is_rootless(EngineType::Other), Some(true));
205+
206+
match old {
207+
Ok(v) => env::set_var(var, v),
208+
Err(_) => env::remove_var(var),
209+
}
210+
}
211+
146212
// determine if the container engine is docker. this fixes issues with
147213
// any aliases (#530), and doesn't fail if an executable suffix exists.
148214
fn get_engine_info(

src/docker/local.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ pub(crate) fn run(
7373
docker
7474
.add_seccomp(engine.kind, &options.target, &paths.metadata)
7575
.wrap_err("when copying seccomp profile")?;
76-
docker.add_user_id(engine.kind);
76+
docker.add_user_id(engine.is_rootless);
7777

7878
docker
7979
.args([

src/docker/remote.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -972,7 +972,7 @@ symlink_recurse \"${{prefix}}\"
972972

973973
// 6. execute our cargo command inside the container
974974
let mut docker = engine.subcommand("exec");
975-
docker.add_user_id(engine.kind);
975+
docker.add_user_id(engine.is_rootless);
976976
docker.add_envvars(&options, toolchain_dirs, msg_info)?;
977977
docker.add_cwd(&paths)?;
978978
docker.arg(&container_id);

src/docker/shared.rs

Lines changed: 7 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use super::image::PossibleImage;
1010
use super::Image;
1111
use super::PROVIDED_IMAGES;
1212
use crate::cargo::CargoMetadata;
13-
use crate::config::{bool_from_envvar, Config};
13+
use crate::config::Config;
1414
use crate::errors::*;
1515
use crate::extensions::{CommandExt, SafeCommand};
1616
use crate::file::{self, write_file, PathExt, ToUtf8};
@@ -947,7 +947,7 @@ pub(crate) trait DockerCommandExt {
947947
) -> Result<()>;
948948
fn add_cwd(&mut self, paths: &DockerPaths) -> Result<()>;
949949
fn add_build_command(&mut self, dirs: &ToolchainDirectories, cmd: &SafeCommand) -> &mut Self;
950-
fn add_user_id(&mut self, engine_type: EngineType);
950+
fn add_user_id(&mut self, is_rootless: bool);
951951
fn add_userns(&mut self);
952952
fn add_seccomp(
953953
&mut self,
@@ -1094,17 +1094,10 @@ impl DockerCommandExt for Command {
10941094
self.args(["sh", "-c", &build_command])
10951095
}
10961096

1097-
fn add_user_id(&mut self, engine_type: EngineType) {
1097+
fn add_user_id(&mut self, is_rootless: bool) {
10981098
// by default, docker runs as root so we need to specify the user
10991099
// so the resulting file permissions are for the current user.
11001100
// since we can have rootless docker, we provide an override.
1101-
let is_rootless = env::var("CROSS_ROOTLESS_CONTAINER_ENGINE")
1102-
.ok()
1103-
.and_then(|s| match s.as_ref() {
1104-
"auto" => None,
1105-
b => Some(bool_from_envvar(b)),
1106-
})
1107-
.unwrap_or_else(|| engine_type != EngineType::Docker);
11081101
if !is_rootless {
11091102
self.args(["--user", &format!("{}:{}", user_id(), group_id(),)]);
11101103
}
@@ -1519,45 +1512,17 @@ mod tests {
15191512

15201513
#[test]
15211514
fn test_docker_user_id() {
1522-
let var = "CROSS_ROOTLESS_CONTAINER_ENGINE";
1523-
let old = env::var(var);
1524-
env::remove_var(var);
1525-
15261515
let rootful = format!("\"engine\" \"--user\" \"{}:{}\"", id::user(), id::group());
15271516
let rootless = "\"engine\"".to_owned();
15281517

1529-
let test = |engine, expected| {
1518+
let test = |noroot, expected| {
15301519
let mut cmd = Command::new("engine");
1531-
cmd.add_user_id(engine);
1520+
cmd.add_user_id(noroot);
15321521
assert_eq!(expected, &format!("{cmd:?}"));
15331522
};
1534-
test(EngineType::Docker, &rootful);
1535-
test(EngineType::Podman, &rootless);
1536-
test(EngineType::PodmanRemote, &rootless);
1537-
test(EngineType::Other, &rootless);
1538-
1539-
env::set_var(var, "0");
1540-
test(EngineType::Docker, &rootful);
1541-
test(EngineType::Podman, &rootful);
1542-
test(EngineType::PodmanRemote, &rootful);
1543-
test(EngineType::Other, &rootful);
1544-
1545-
env::set_var(var, "1");
1546-
test(EngineType::Docker, &rootless);
1547-
test(EngineType::Podman, &rootless);
1548-
test(EngineType::PodmanRemote, &rootless);
1549-
test(EngineType::Other, &rootless);
1550-
1551-
env::set_var(var, "auto");
1552-
test(EngineType::Docker, &rootful);
1553-
test(EngineType::Podman, &rootless);
1554-
test(EngineType::PodmanRemote, &rootless);
1555-
test(EngineType::Other, &rootless);
15561523

1557-
match old {
1558-
Ok(v) => env::set_var(var, v),
1559-
Err(_) => env::remove_var(var),
1560-
}
1524+
test(false, &rootful);
1525+
test(true, &rootless);
15611526
}
15621527

15631528
#[test]

0 commit comments

Comments
 (0)