use std::ffi::{OsStr, OsString};
use std::fs;
use std::io::{self, Read};
use std::path::{Path, PathBuf};
use std::process::{Command, ExitStatus, Stdio};
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{SystemTime, UNIX_EPOCH};
use crate::model::{Error, Result, ValidSource};
use crate::repository::materialize_source;
const CONTAINERFILE: &str = include_str!("../Containerfile");
const MAX_CAPTURE_BYTES: usize = 8 * 1024;
static RUN_COUNTER: AtomicU64 = AtomicU64::new(0);
pub(crate) fn check(source: &ValidSource) -> Result<()> {
check_with(source, OsStr::new("podman"), &tool_image())
}
pub(crate) fn publish(source: &ValidSource, token: &str) -> Result<()> {
publish_with(source, token, OsStr::new("podman"), &tool_image())
}
fn check_with(source: &ValidSource, podman: &OsStr, image: &str) -> Result<()> {
ensure_image(podman, image)?;
let run = RunDirectory::new(source, "check")?;
for stage in stages() {
run_stage(podman, image, &run, stage)?;
}
Ok(())
}
fn publish_with(source: &ValidSource, token: &str, podman: &OsStr, image: &str) -> Result<()> {
check_with(source, podman, image)?;
ensure_image(podman, image)?;
let run = RunDirectory::new(source, "publish")?;
let mut command = podman_command(podman, &run, "/tmp", true);
command
.env("CARGO_REGISTRY_TOKEN", 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");
run_checked(command, "publish", "cargo publish").map_err(|error| error.redact(token))
}
struct Stage {
category: &'static str,
label: &'static str,
arguments: &'static [&'static str],
network: bool,
}
fn stages() -> [Stage; 6] {
[
Stage {
category: "check.fetch",
label: "cargo fetch",
arguments: &["fetch", "--color", "never"],
network: true,
},
Stage {
category: "check.format",
label: "cargo fmt",
arguments: &["fmt", "--all", "--", "--check"],
network: false,
},
Stage {
category: "check.build",
label: "cargo build",
arguments: &[
"build",
"--workspace",
"--all-targets",
"--all-features",
"--locked",
"--offline",
"--color",
"never",
],
network: false,
},
Stage {
category: "check.clippy",
label: "cargo clippy",
arguments: &[
"clippy",
"--workspace",
"--all-targets",
"--all-features",
"--locked",
"--offline",
"--color",
"never",
"--",
"-D",
"warnings",
],
network: false,
},
Stage {
category: "check.test",
label: "cargo test",
arguments: &[
"test",
"--workspace",
"--all-targets",
"--all-features",
"--locked",
"--offline",
"--no-fail-fast",
"--color",
"never",
],
network: false,
},
Stage {
category: "check.doc_test",
label: "cargo doc tests",
arguments: &[
"test",
"--workspace",
"--all-features",
"--doc",
"--locked",
"--offline",
"--no-fail-fast",
"--color",
"never",
],
network: false,
},
]
}
fn run_stage(podman: &OsStr, image: &str, run: &RunDirectory, stage: Stage) -> Result<()> {
let mut command = podman_command(podman, run, "/workspace", stage.network);
command.arg(image).arg("cargo").args(stage.arguments);
run_checked(command, stage.category, stage.label)
}
fn ensure_image(podman: &OsStr, image: &str) -> Result<()> {
let mut inspect = Command::new(podman);
inspect.arg("image").arg("exists").arg(image);
let inspected = run_capture(inspect, "inspect Podman image")?;
if inspected.status.success() {
return Ok(());
}
if inspected.status.code() != Some(1) {
return Err(command_error(
"sandbox.image",
"Podman image inspection",
inspected,
));
}
let build = TemporaryDirectory::new("image")?;
let containerfile = build.path().join("Containerfile");
fs::write(&containerfile, CONTAINERFILE)
.map_err(|error| Error::io("write embedded Containerfile", &containerfile, error))?;
let mut command = Command::new(podman);
command
.arg("build")
.arg("--tag")
.arg(image)
.arg("--file")
.arg(&containerfile)
.arg(build.path());
run_checked(command, "sandbox.image", "Podman image build")
}
fn podman_command(podman: &OsStr, run: &RunDirectory, workdir: &str, network: bool) -> Command {
let mut command = Command::new(podman);
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");
if !network {
command.arg("--network=none");
}
command
.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 specification = source.as_os_str().to_os_string();
specification.push(":");
specification.push(destination);
specification.push(":rw,Z");
specification
}
fn run_checked(command: Command, category: &'static str, label: &'static str) -> Result<()> {
let output = run_capture(command, label)?;
if output.status.success() {
Ok(())
} else {
Err(command_error(category, label, output))
}
}
fn run_capture(mut command: Command, label: &'static str) -> Result<CapturedOutput> {
command.stdout(Stdio::piped()).stderr(Stdio::piped());
let mut child = command.spawn().map_err(|error| {
Error::new("sandbox.start", format!("could not start {label}: {error}"))
})?;
let stdout = child
.stdout
.take()
.ok_or_else(|| Error::new("sandbox.start", format!("could not capture {label} stdout")))?;
let stderr = child
.stderr
.take()
.ok_or_else(|| Error::new("sandbox.start", format!("could not capture {label} stderr")))?;
let (status, stdout, stderr) = std::thread::scope(|scope| {
let stdout_thread = scope.spawn(|| capture_stream(stdout));
let stderr_thread = scope.spawn(|| capture_stream(stderr));
let status = child.wait();
let stdout = stdout_thread.join();
let stderr = stderr_thread.join();
(status, stdout, stderr)
});
let status = status.map_err(|error| {
Error::new(
"sandbox.wait",
format!("could not wait for {label}: {error}"),
)
})?;
let stdout = stdout
.map_err(|_| Error::new("sandbox.capture", format!("{label} stdout reader panicked")))?
.map_err(|error| {
Error::new(
"sandbox.capture",
format!("could not read {label} stdout: {error}"),
)
})?;
let stderr = stderr
.map_err(|_| Error::new("sandbox.capture", format!("{label} stderr reader panicked")))?
.map_err(|error| {
Error::new(
"sandbox.capture",
format!("could not read {label} stderr: {error}"),
)
})?;
Ok(CapturedOutput {
status,
stdout,
stderr,
})
}
fn capture_stream(mut reader: impl Read) -> io::Result<CapturedStream> {
let mut retained = Vec::with_capacity(MAX_CAPTURE_BYTES);
let mut buffer = [0_u8; 4096];
let mut truncated = false;
loop {
let read = reader.read(&mut buffer)?;
if read == 0 {
break;
}
let remaining = MAX_CAPTURE_BYTES.saturating_sub(retained.len());
let keep = remaining.min(read);
retained.extend_from_slice(&buffer[..keep]);
if keep < read {
truncated = true;
}
}
Ok(CapturedStream {
text: String::from_utf8_lossy(&retained).into_owned(),
truncated,
})
}
fn command_error(category: &'static str, label: &str, output: CapturedOutput) -> Error {
let mut detail = if output.stderr.text.trim().is_empty() {
output.stdout.text.trim().to_owned()
} else {
output.stderr.text.trim().to_owned()
};
if detail.is_empty() {
detail = "no diagnostic output".to_owned();
}
if output.stdout.truncated || output.stderr.truncated {
detail.push_str("\n[diagnostic truncated]");
}
let status = output
.status
.code()
.map_or_else(|| "signal".to_owned(), |code| code.to_string());
Error::new(category, format!("{label} exited with {status}: {detail}"))
}
struct CapturedOutput {
status: ExitStatus,
stdout: CapturedStream,
stderr: CapturedStream,
}
struct CapturedStream {
text: String,
truncated: bool,
}
struct RunDirectory {
_root: TemporaryDirectory,
workspace: PathBuf,
cargo_home: PathBuf,
target: PathBuf,
}
impl RunDirectory {
fn new(source: &ValidSource, label: &str) -> Result<Self> {
let root = TemporaryDirectory::new(label)?;
let workspace = root.path().join("workspace");
let cargo_home = root.path().join("cargo-home");
let target = root.path().join("target");
fs::create_dir(&workspace)
.map_err(|error| Error::io("create disposable workspace", &workspace, error))?;
materialize_source(&workspace, &source.files)?;
fs::create_dir(&cargo_home)
.map_err(|error| Error::io("create disposable Cargo home", &cargo_home, error))?;
fs::create_dir(&target)
.map_err(|error| Error::io("create disposable target", &target, error))?;
Ok(Self {
_root: root,
workspace,
cargo_home,
target,
})
}
}
struct TemporaryDirectory(PathBuf);
impl TemporaryDirectory {
fn new(label: &str) -> Result<Self> {
let parent = std::env::temp_dir().join("kcode-rust-libs-v2");
fs::create_dir_all(&parent)
.map_err(|error| Error::io("create temporary work root", &parent, error))?;
for _ in 0..100 {
let nanos = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_nanos();
let counter = RUN_COUNTER.fetch_add(1, Ordering::Relaxed);
let path = parent.join(format!(
"{label}-{}-{nanos:x}-{counter:x}",
std::process::id()
));
match fs::create_dir(&path) {
Ok(()) => return Ok(Self(path)),
Err(error) if error.kind() == io::ErrorKind::AlreadyExists => continue,
Err(error) => return Err(Error::io("create temporary directory", path, error)),
}
}
Err(Error::new(
"sandbox.prepare",
"could not allocate a unique temporary directory",
))
}
fn path(&self) -> &Path {
&self.0
}
}
impl Drop for TemporaryDirectory {
fn drop(&mut self) {
let _ = fs::remove_dir_all(&self.0);
}
}
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-v2-toolchain:{}-{hash:016x}",
env!("CARGO_PKG_VERSION")
)
}
#[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 crate::model::{File, validate_source};
use super::{check_with, publish_with};
static TEST_COUNTER: AtomicU64 = AtomicU64::new(0);
struct Fixture(PathBuf);
impl Fixture {
fn new(label: &str) -> Self {
let counter = TEST_COUNTER.fetch_add(1, Ordering::Relaxed);
let path = std::env::temp_dir().join(format!(
"kcode-rust-libs-v2-sandbox-{label}-{}-{counter}",
std::process::id()
));
fs::create_dir(&path).unwrap();
Self(path)
}
fn path(&self) -> &Path {
&self.0
}
fn script(&self, contents: &str) -> PathBuf {
let path = self.path().join("fake-podman");
fs::write(&path, contents).unwrap();
let mut permissions = fs::metadata(&path).unwrap().permissions();
permissions.set_mode(0o755);
fs::set_permissions(&path, permissions).unwrap();
path
}
}
impl Drop for Fixture {
fn drop(&mut self) {
let _ = fs::remove_dir_all(&self.0);
}
}
fn source() -> crate::model::ValidSource {
validate_source(
&[
File {
path: "Cargo.toml".to_owned(),
contents:
"[package]\nname = \"demo\"\nversion = \"0.1.0\"\nedition = \"2024\"\n"
.to_owned(),
},
File {
path: "Documentation.md".to_owned(),
contents: "docs\n".to_owned(),
},
File {
path: "src/lib.rs".to_owned(),
contents: String::new(),
},
],
"demo",
)
.unwrap()
}
#[test]
fn check_and_publish_success_are_unit_shaped() {
let fixture = Fixture::new("success");
let log = fixture.path().join("arguments.log");
let podman = fixture.script(&format!(
"#!/bin/sh\nif [ \"$1\" = image ]; then exit 0; fi\nprintf '%s\\n' \"$*\" >> '{}'\nexit 0\n",
log.display()
));
let checked: () = check_with(&source(), podman.as_os_str(), "test-image").unwrap();
let published: () =
publish_with(&source(), "private-token", podman.as_os_str(), "test-image").unwrap();
assert_eq!(checked, ());
assert_eq!(published, ());
let arguments = fs::read_to_string(log).unwrap();
assert!(!arguments.contains("private-token"));
}
#[test]
fn publication_diagnostics_are_bounded_and_redacted() {
let fixture = Fixture::new("failure");
let podman = fixture.script(
"#!/bin/sh\nif [ \"$1\" = image ]; then exit 0; fi\ncase \"$*\" in\n *' publish '*)\n i=0\n while [ $i -lt 2000 ]; do printf 'private-token diagnostic text '; i=$((i+1)); done >&2\n exit 1\n ;;\nesac\nexit 0\n",
);
let error =
publish_with(&source(), "private-token", podman.as_os_str(), "test-image").unwrap_err();
let rendered = error.to_string();
assert!(!rendered.contains("private-token"));
assert!(rendered.contains("[REDACTED]"));
assert!(rendered.contains("[diagnostic truncated]"));
assert!(rendered.len() < 13 * 1024);
}
}