use std::ffi::{OsStr, OsString};
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{SystemTime, UNIX_EPOCH};
use crate::files::copy_tree;
use crate::{CheckResult, CheckStage, CheckStageResult, Error, Result};
static RUN_COUNTER: AtomicU64 = AtomicU64::new(0);
const CONTAINERFILE: &str = include_str!("../Containerfile");
const PODMAN_PROGRAM: &str = "podman";
struct RunDirectory {
root: PathBuf,
workspace: PathBuf,
cargo_home: PathBuf,
target: PathBuf,
}
impl RunDirectory {
fn create(work_root: &Path, rust_lib: &Path) -> Result<Self> {
let runs_root = work_root.join("runs");
fs::create_dir_all(&runs_root)
.map_err(|source| Error::io("create runs directory", &runs_root, source))?;
let root = create_unique_directory(&runs_root, "run")?;
let workspace = root.join("workspace");
let cargo_home = root.join("cargo-home");
let target = root.join("target");
let prepared = (|| {
copy_tree(rust_lib, &workspace)?;
fs::create_dir(&cargo_home)
.map_err(|source| Error::io("create Cargo home", &cargo_home, source))?;
fs::create_dir(&target)
.map_err(|source| Error::io("create target directory", &target, source))?;
Ok(())
})();
if let Err(error) = prepared {
let _ = fs::remove_dir_all(&root);
return Err(error);
}
Ok(Self {
root,
workspace,
cargo_home,
target,
})
}
}
impl Drop for RunDirectory {
fn drop(&mut self) {
let _ = fs::remove_dir_all(&self.root);
}
}
fn create_unique_directory(parent: &Path, prefix: &str) -> Result<PathBuf> {
for _ in 0..100 {
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_nanos();
let counter = RUN_COUNTER.fetch_add(1, Ordering::Relaxed);
let candidate = parent.join(format!(
"{prefix}-{}-{timestamp}-{counter}",
std::process::id()
));
match fs::create_dir(&candidate) {
Ok(()) => return Ok(candidate),
Err(source) if source.kind() == std::io::ErrorKind::AlreadyExists => continue,
Err(source) => {
return Err(Error::io(
"create validation run directory",
candidate,
source,
));
}
}
}
Err(Error::Sandbox {
stage: "prepare".to_owned(),
message: "could not allocate a unique run directory".to_owned(),
})
}
pub(crate) fn check(rust_lib: &Path, work_root: &Path) -> Result<CheckResult> {
let image = tool_image();
check_with_runtime(rust_lib, work_root, OsStr::new(PODMAN_PROGRAM), &image)
}
fn check_with_runtime(
rust_lib: &Path,
work_root: &Path,
podman_program: &OsStr,
image: &str,
) -> Result<CheckResult> {
ensure_image(work_root, podman_program, image)?;
let run = RunDirectory::create(work_root, rust_lib)?;
let pipeline: &[(CheckStage, bool, &[&str])] = &[
(CheckStage::Fetch, true, &["fetch", "--color", "never"]),
(
CheckStage::Format,
false,
&["fmt", "--all", "--", "--check"],
),
(
CheckStage::Build,
false,
&[
"build",
"--workspace",
"--all-targets",
"--all-features",
"--locked",
"--offline",
"--color",
"never",
],
),
(
CheckStage::Clippy,
false,
&[
"clippy",
"--workspace",
"--all-targets",
"--all-features",
"--locked",
"--offline",
"--color",
"never",
"--",
"-D",
"warnings",
],
),
(
CheckStage::Test,
false,
&[
"test",
"--workspace",
"--all-targets",
"--all-features",
"--locked",
"--offline",
"--no-fail-fast",
"--color",
"never",
],
),
(
CheckStage::DocTest,
false,
&[
"test",
"--workspace",
"--all-features",
"--doc",
"--locked",
"--offline",
"--no-fail-fast",
"--color",
"never",
],
),
];
let mut stages = Vec::with_capacity(pipeline.len());
for &(stage, networked, cargo_arguments) in pipeline {
let result = run_stage(
podman_program,
image,
&run,
stage,
networked,
cargo_arguments,
)?;
let success = result.success;
stages.push(result);
if !success {
break;
}
}
Ok(CheckResult { stages })
}
fn run_stage(
podman_program: &OsStr,
image: &str,
run: &RunDirectory,
stage: CheckStage,
networked: bool,
cargo_arguments: &[&str],
) -> Result<CheckStageResult> {
let mut command = podman_run_command(podman_program, run, "/workspace");
if !networked {
command.arg("--network=none");
}
command.arg(image).arg("cargo").args(cargo_arguments);
let output = command.output().map_err(|source| Error::Sandbox {
stage: stage.to_string(),
message: format!("could not start {:?}: {source}", podman_program),
})?;
let exit_code = output.status.code();
let stdout = String::from_utf8_lossy(&output.stdout).into_owned();
let stderr = String::from_utf8_lossy(&output.stderr).into_owned();
if matches!(exit_code, Some(125..=127)) {
let detail = if stderr.trim().is_empty() {
stdout.trim()
} else {
stderr.trim()
};
return Err(Error::Sandbox {
stage: stage.to_string(),
message: format!(
"Podman exited with status {}; {detail}",
exit_code.expect("matched exit code")
),
});
}
Ok(CheckStageResult {
stage,
success: output.status.success(),
exit_code,
stdout,
stderr,
})
}
pub(crate) fn publish(rust_lib: &Path, work_root: &Path, registry_token: &str) -> Result<()> {
let image = tool_image();
publish_with_runtime(
rust_lib,
work_root,
registry_token,
OsStr::new(PODMAN_PROGRAM),
&image,
)
}
fn publish_with_runtime(
rust_lib: &Path,
work_root: &Path,
registry_token: &str,
podman_program: &OsStr,
image: &str,
) -> Result<()> {
ensure_image(work_root, podman_program, image)?;
let run = RunDirectory::create(work_root, rust_lib)?;
let mut command = podman_run_command(podman_program, &run, "/tmp");
command
.env("CARGO_REGISTRY_TOKEN", registry_token)
.arg("--env=CARGO_REGISTRY_TOKEN")
.arg(image)
.arg("cargo")
.arg("--config")
.arg("registry.global-credential-providers=[\"cargo:token\"]")
.arg("publish")
.arg("--manifest-path=/workspace/Cargo.toml")
.arg("--registry=crates-io")
.arg("--no-verify")
.arg("--color=never");
let output = command.output().map_err(|source| Error::Sandbox {
stage: "publish".to_owned(),
message: format!("could not start {:?}: {source}", podman_program),
})?;
if matches!(output.status.code(), Some(125..=127)) {
return Err(Error::Sandbox {
stage: "publish".to_owned(),
message: redact_secret(
command_failure("Podman publication container", &output),
registry_token,
),
});
}
if !output.status.success() {
return Err(Error::Publish(redact_secret(
command_failure("cargo publish", &output),
registry_token,
)));
}
Ok(())
}
fn podman_run_command(podman_program: &OsStr, run: &RunDirectory, workdir: &str) -> Command {
let mut command = Command::new(podman_program);
command
.arg("run")
.arg("--rm")
.arg("--read-only")
.arg("--cap-drop=all")
.arg("--security-opt=no-new-privileges")
.arg("--userns=keep-id")
.arg("--tmpfs=/tmp:rw,nosuid,nodev")
.arg("--workdir")
.arg(workdir)
.arg("--env=CARGO_HOME=/cargo-home")
.arg("--env=CARGO_TARGET_DIR=/target")
.arg("--env=CARGO_TERM_COLOR=never")
.arg("--pull=never")
.arg("--volume")
.arg(volume_spec(&run.workspace, "/workspace"))
.arg("--volume")
.arg(volume_spec(&run.cargo_home, "/cargo-home"))
.arg("--volume")
.arg(volume_spec(&run.target, "/target"));
command
}
fn volume_spec(source: &Path, destination: &str) -> OsString {
let mut value = source.as_os_str().to_os_string();
value.push(":");
value.push(destination);
value.push(":rw,Z");
value
}
fn tool_image() -> String {
let mut hash = 0xcbf29ce484222325_u64;
for byte in CONTAINERFILE.bytes() {
hash ^= u64::from(byte);
hash = hash.wrapping_mul(0x100000001b3);
}
format!(
"localhost/kcode-rust-libs-toolchain:{}-{hash:016x}",
env!("CARGO_PKG_VERSION")
)
}
fn ensure_image(work_root: &Path, podman_program: &OsStr, image: &str) -> Result<()> {
let inspect = Command::new(podman_program)
.arg("image")
.arg("exists")
.arg(image)
.output()
.map_err(|source| Error::Sandbox {
stage: "prepare-image".to_owned(),
message: format!("could not start {:?}: {source}", podman_program),
})?;
if inspect.status.success() {
return Ok(());
}
if inspect.status.code() != Some(1) {
return Err(Error::Sandbox {
stage: "prepare-image".to_owned(),
message: command_failure("Podman image inspection", &inspect),
});
}
let builds_root = work_root.join("image-builds");
fs::create_dir_all(&builds_root)
.map_err(|source| Error::io("create image-build directory", &builds_root, source))?;
let build_root = create_unique_directory(&builds_root, "build")?;
let build = ImageBuildDirectory(build_root);
let containerfile = build.0.join("Containerfile");
fs::write(&containerfile, CONTAINERFILE)
.map_err(|source| Error::io("write embedded Containerfile", &containerfile, source))?;
let output = Command::new(podman_program)
.arg("build")
.arg("--tag")
.arg(image)
.arg("--file")
.arg(&containerfile)
.arg(&build.0)
.output()
.map_err(|source| Error::Sandbox {
stage: "prepare-image".to_owned(),
message: format!("could not start {:?}: {source}", podman_program),
})?;
if !output.status.success() {
return Err(Error::Sandbox {
stage: "prepare-image".to_owned(),
message: command_failure("Podman image build", &output),
});
}
Ok(())
}
struct ImageBuildDirectory(PathBuf);
impl Drop for ImageBuildDirectory {
fn drop(&mut self) {
let _ = fs::remove_dir_all(&self.0);
}
}
fn command_failure(operation: &str, output: &std::process::Output) -> String {
let stderr = String::from_utf8_lossy(&output.stderr);
let stdout = String::from_utf8_lossy(&output.stdout);
let detail = if stderr.trim().is_empty() {
stdout.trim()
} else {
stderr.trim()
};
format!(
"{operation} exited with status {}; {detail}",
output
.status
.code()
.map_or_else(|| "signal".to_owned(), |code| code.to_string())
)
}
fn redact_secret(message: String, secret: &str) -> String {
message.replace(secret, "[REDACTED]")
}
#[cfg(all(test, unix))]
mod tests {
use std::fs;
use std::os::unix::fs::PermissionsExt;
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{SystemTime, UNIX_EPOCH};
use super::{check_with_runtime, publish_with_runtime};
use crate::CheckStage;
static TEMP_COUNTER: AtomicU64 = AtomicU64::new(0);
struct TestDirectory(PathBuf);
impl TestDirectory {
fn new(label: &str) -> Self {
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_nanos();
let counter = TEMP_COUNTER.fetch_add(1, Ordering::Relaxed);
let path = std::env::temp_dir().join(format!(
"kcode-rust-libs-sandbox-test-{label}-{}-{timestamp}-{counter}",
std::process::id()
));
fs::create_dir(&path).unwrap();
Self(path)
}
fn path(&self) -> &Path {
&self.0
}
fn fake_podman(&self, script: &str) -> PathBuf {
let path = self.path().join("fake-podman");
fs::write(&path, script).unwrap();
let mut permissions = fs::metadata(&path).unwrap().permissions();
permissions.set_mode(0o755);
fs::set_permissions(&path, permissions).unwrap();
path
}
fn repository(&self) -> PathBuf {
let repository = self.path().join("repository");
fs::create_dir_all(repository.join("src")).unwrap();
fs::write(
repository.join("Cargo.toml"),
"[package]\nname = \"test\"\nversion = \"0.1.0\"\nedition = \"2024\"\n",
)
.unwrap();
fs::write(repository.join("src/lib.rs"), "").unwrap();
fs::write(repository.join("Documentation.md"), "test docs\n").unwrap();
fs::write(repository.join("Version.txt"), "0.1.0\n").unwrap();
repository
}
}
impl Drop for TestDirectory {
fn drop(&mut self) {
let _ = fs::remove_dir_all(&self.0);
}
}
#[test]
fn uses_disposable_work_directories_and_fixed_stages() {
let test = TestDirectory::new("pipeline");
let log = test.path().join("podman.log");
let script = format!(
"#!/bin/sh\nif [ \"$1\" = image ]; then exit 0; fi\nprintf '%s\\n' \"$*\" >> '{}'\nexit 0\n",
log.display()
);
let fake_podman = test.fake_podman(&script);
let repository = test.repository();
let work = test.path().join("work");
fs::create_dir(&work).unwrap();
let result = check_with_runtime(
&repository,
&work,
fake_podman.as_os_str(),
"rust-tools:test",
)
.unwrap();
assert!(result.passed());
assert!(!repository.join("target").exists());
assert!(!repository.join("Cargo.lock").exists());
let lines = fs::read_to_string(log).unwrap();
let lines = lines.lines().collect::<Vec<_>>();
assert_eq!(lines.len(), 6);
assert!(!lines[0].contains("--network=none"));
assert!(
lines[1..]
.iter()
.all(|line| line.contains("--network=none"))
);
assert!(fs::read_dir(work.join("runs")).unwrap().next().is_none());
}
#[test]
fn returns_quality_failures_and_stops_the_pipeline() {
let test = TestDirectory::new("failure");
let fake_podman = test.fake_podman(
"#!/bin/sh\nif [ \"$1\" = image ]; then exit 0; fi\ncase \" $* \" in\n *\" cargo fmt \"*) echo format-stdout; echo format-stderr >&2; exit 1 ;;\nesac\nexit 0\n",
);
let repository = test.repository();
let work = test.path().join("work");
fs::create_dir(&work).unwrap();
let result = check_with_runtime(
&repository,
&work,
fake_podman.as_os_str(),
"rust-tools:test",
)
.unwrap();
assert!(!result.passed());
assert_eq!(result.stages.len(), 2);
let failure = result.failure().unwrap();
assert_eq!(failure.stage, CheckStage::Format);
assert_eq!(failure.exit_code, Some(1));
assert_eq!(failure.stdout, "format-stdout\n");
assert_eq!(failure.stderr, "format-stderr\n");
}
#[test]
fn builds_the_embedded_tool_image_when_missing() {
let test = TestDirectory::new("image");
let log = test.path().join("podman.log");
let script = format!(
"#!/bin/sh\nprintf '%s\\n' \"$*\" >> '{}'\nif [ \"$1\" = image ]; then exit 1; fi\nexit 0\n",
log.display()
);
let fake_podman = test.fake_podman(&script);
let repository = test.repository();
let work = test.path().join("work");
fs::create_dir(&work).unwrap();
let result = check_with_runtime(
&repository,
&work,
fake_podman.as_os_str(),
"rust-tools:test",
)
.unwrap();
assert!(result.passed());
let lines = fs::read_to_string(log).unwrap();
let lines = lines.lines().collect::<Vec<_>>();
assert!(lines[0].starts_with("image exists rust-tools:test"));
assert!(lines[1].starts_with("build --tag rust-tools:test"));
assert!(
fs::read_dir(work.join("image-builds"))
.unwrap()
.next()
.is_none()
);
}
#[test]
fn publishes_without_putting_the_token_or_library_config_on_the_command_line() {
let test = TestDirectory::new("publish");
let log = test.path().join("podman.log");
let script = format!(
"#!/bin/sh\nif [ \"$1\" = image ]; then exit 0; fi\nprintf '%s\\n' \"$*\" >> '{}'\n[ \"$CARGO_REGISTRY_TOKEN\" = publish-secret ] || exit 90\ncase \"$*\" in *publish-secret*) exit 91 ;; esac\nexit 0\n",
log.display()
);
let fake_podman = test.fake_podman(&script);
let repository = test.repository();
fs::create_dir(repository.join(".cargo")).unwrap();
fs::write(
repository.join(".cargo/config.toml"),
"[registry]\ndefault = \"untrusted\"\n",
)
.unwrap();
let work = test.path().join("work");
fs::create_dir(&work).unwrap();
publish_with_runtime(
&repository,
&work,
"publish-secret",
fake_podman.as_os_str(),
"rust-tools:test",
)
.unwrap();
let line = fs::read_to_string(log).unwrap();
assert!(line.contains("--env=CARGO_REGISTRY_TOKEN"));
assert!(!line.contains("publish-secret"));
assert!(line.contains("--workdir /tmp"));
assert!(line.contains("--manifest-path=/workspace/Cargo.toml"));
assert!(line.contains("--registry=crates-io"));
assert!(line.contains("--no-verify"));
assert!(line.contains("cargo:token"));
assert!(fs::read_dir(work.join("runs")).unwrap().next().is_none());
}
#[test]
fn redacts_the_registry_token_from_publication_errors() {
let test = TestDirectory::new("publish-redaction");
let fake_podman = test.fake_podman(
"#!/bin/sh\nif [ \"$1\" = image ]; then exit 0; fi\necho \"rejected $CARGO_REGISTRY_TOKEN\" >&2\nexit 1\n",
);
let repository = test.repository();
let work = test.path().join("work");
fs::create_dir(&work).unwrap();
let error = publish_with_runtime(
&repository,
&work,
"publish-secret",
fake_podman.as_os_str(),
"rust-tools:test",
)
.unwrap_err();
let message = error.to_string();
assert!(!message.contains("publish-secret"));
assert!(message.contains("[REDACTED]"));
}
}