use std::os::unix::fs::MetadataExt;
use std::path::PathBuf;
use anyhow::{Context, bail};
use nix::unistd::{Gid, Group};
use crate::constants::{DOCKER_GROUP_NAME, DOCKER_HOST_ENV, GROUPS_ENV, UNIX_SOCKET_SCHEME};
#[derive(Debug, Clone, PartialEq)]
pub struct DockerSocket {
pub path: PathBuf,
pub group: String,
pub gid: u32,
}
impl DockerSocket {
pub fn locate(endpoint: &str) -> anyhow::Result<Self> {
let path = unix_socket_path(endpoint)?;
let gid = std::fs::metadata(&path)
.with_context(|| format!("reading the docker socket {}", path.display()))?
.gid();
let group = Group::from_gid(Gid::from_raw(gid))
.ok()
.flatten()
.map_or_else(|| DOCKER_GROUP_NAME.to_owned(), |group| group.name);
Ok(Self { path, group, gid })
}
pub fn env(&self) -> [(&'static str, String); 2] {
[
(
DOCKER_HOST_ENV,
format!("{UNIX_SOCKET_SCHEME}{}", self.path.display()),
),
(GROUPS_ENV, format!("{}:{}", self.group, self.gid)),
]
}
}
fn unix_socket_path(endpoint: &str) -> anyhow::Result<PathBuf> {
match endpoint.trim().strip_prefix(UNIX_SOCKET_SCHEME) {
Some(path) if !path.is_empty() => Ok(PathBuf::from(path)),
_ => bail!(
"share.docker needs a local unix socket, but the docker endpoint is `{}`; \
point DOCKER_HOST or `docker context use` at a unix:// socket",
endpoint.trim()
),
}
}
#[cfg(test)]
#[allow(non_snake_case)] mod tests {
use super::*;
#[test]
fn unix_socket_path__unix_endpoint__its_socket_path() {
let accepted = [
("unix:///var/run/docker.sock", "/var/run/docker.sock"),
(
"unix:///run/user/1000/docker.sock\n",
"/run/user/1000/docker.sock",
),
];
for (endpoint, expected) in accepted {
let path = unix_socket_path(endpoint).unwrap();
assert_eq!(path, PathBuf::from(expected), "endpoint: {endpoint:?}");
}
}
#[test]
fn unix_socket_path__remote_endpoint__is_refused_naming_it() {
let refused = [
(
"tcp://10.0.0.1:2375",
"the docker endpoint is `tcp://10.0.0.1:2375`",
),
(
"ssh://sally@build-host",
"the docker endpoint is `ssh://sally@build-host`",
),
("unix://", "the docker endpoint is `unix://`"),
("", "the docker endpoint is ``"),
];
for (endpoint, expected) in refused {
let error = unix_socket_path(endpoint).unwrap_err().to_string();
assert!(error.contains(expected), "{error}");
}
}
#[test]
fn env__socket__docker_host_and_group() {
let socket = DockerSocket {
path: PathBuf::from("/var/run/docker.sock"),
group: "docker".to_owned(),
gid: 969,
};
let env = socket.env();
assert_eq!(
env,
[
("DOCKER_HOST", "unix:///var/run/docker.sock".to_owned()),
("VZ_GROUPS", "docker:969".to_owned()),
]
);
}
}