refactor: split ecr into library and cli crates
The root package becomes a virtual workspace: `crates/ecr` holds the library (package name `ecr`) and `crates/ecr-cli` the command line front-end, which keeps installing the `ecr` binary. No behavior change. The library must not depend on CLI types, so mount::setup_mounts now takes the `no_bind` flag instead of a `&Args`.
This commit is contained in:
Generated
+11
-1
@@ -302,7 +302,6 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"base64",
|
"base64",
|
||||||
"clap",
|
|
||||||
"cpio",
|
"cpio",
|
||||||
"dirs",
|
"dirs",
|
||||||
"flate2",
|
"flate2",
|
||||||
@@ -323,6 +322,17 @@ dependencies = [
|
|||||||
"zstd",
|
"zstd",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ecr-cli"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"clap",
|
||||||
|
"dirs",
|
||||||
|
"ecr",
|
||||||
|
"tempfile",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "either"
|
name = "either"
|
||||||
version = "1.16.0"
|
version = "1.16.0"
|
||||||
|
|||||||
+5
-41
@@ -1,50 +1,14 @@
|
|||||||
[package]
|
[workspace]
|
||||||
name = "ecr"
|
resolver = "2"
|
||||||
|
members = ["crates/ecr", "crates/ecr-cli"]
|
||||||
|
|
||||||
|
[workspace.package]
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
rust-version = "1.77"
|
rust-version = "1.77"
|
||||||
description = "Enter chroot environments with Linux namespaces"
|
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
authors = ["Valentin Haudiquet"]
|
authors = ["Valentin Haudiquet"]
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
# CLI parsing
|
|
||||||
clap = { version = "4", features = ["derive", "env"] }
|
|
||||||
|
|
||||||
# Config parsing
|
|
||||||
serde = { version = "1", features = ["derive"] }
|
|
||||||
serde_yaml = "0.9"
|
|
||||||
|
|
||||||
# HTTP downloads
|
|
||||||
reqwest = { version = "0.13", features = ["blocking", "stream"] }
|
|
||||||
|
|
||||||
# Tarball extraction
|
|
||||||
tar = "0.4"
|
|
||||||
flate2 = "1"
|
|
||||||
xz2 = "0.1"
|
|
||||||
zstd = "0.13"
|
|
||||||
|
|
||||||
# Unix syscall bindings
|
|
||||||
nix = { version = "0.31", features = ["fs", "mount", "sched", "signal", "user", "process", "hostname"] }
|
|
||||||
|
|
||||||
# Temp directories
|
|
||||||
tempfile = "3"
|
|
||||||
|
|
||||||
# Error handling
|
|
||||||
anyhow = "1"
|
|
||||||
|
|
||||||
# Utilities
|
|
||||||
dirs = "6"
|
|
||||||
which = "7"
|
|
||||||
cpio = "0.4"
|
|
||||||
base64 = "0.22"
|
|
||||||
tokio = { version = "1", features = ["rt-multi-thread", "macros", "io-util"] }
|
|
||||||
futures-util = "0.3"
|
|
||||||
indicatif = "0.18"
|
|
||||||
serde_json = "1"
|
|
||||||
libc = "0.2"
|
|
||||||
users = "0.11"
|
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
strip = true
|
strip = true
|
||||||
opt-level = "z"
|
opt-level = "z"
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
[package]
|
||||||
|
name = "ecr-cli"
|
||||||
|
description = "Enter chroot environments with Linux namespaces"
|
||||||
|
version.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
rust-version.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
authors.workspace = true
|
||||||
|
|
||||||
|
# The installed binary keeps the `ecr` name even though the CLI package is
|
||||||
|
# `ecr-cli`; the library package owns the `ecr` name for consumers.
|
||||||
|
[[bin]]
|
||||||
|
name = "ecr"
|
||||||
|
path = "src/main.rs"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
ecr = { path = "../ecr" }
|
||||||
|
|
||||||
|
# CLI parsing
|
||||||
|
clap = { version = "4", features = ["derive", "env"] }
|
||||||
|
|
||||||
|
# Error handling
|
||||||
|
anyhow = "1"
|
||||||
|
|
||||||
|
# Interim: used by the CLI's inline cache/extraction orchestration until it
|
||||||
|
# moves behind the library's rootfs API
|
||||||
|
dirs = "6"
|
||||||
|
tempfile = "3"
|
||||||
@@ -1,37 +1,17 @@
|
|||||||
mod chroot;
|
|
||||||
mod cli;
|
mod cli;
|
||||||
mod config;
|
|
||||||
mod distro;
|
|
||||||
mod download;
|
|
||||||
mod extract;
|
|
||||||
mod kernel;
|
|
||||||
mod mount;
|
|
||||||
mod namespace;
|
|
||||||
mod qemu;
|
|
||||||
mod qemu_vm;
|
|
||||||
mod utils;
|
|
||||||
mod verbose;
|
|
||||||
|
|
||||||
/// Print to stderr only when --verbose / -v is active.
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! veprintln {
|
|
||||||
($($arg:tt)*) => {
|
|
||||||
if $crate::verbose::is_verbose() {
|
|
||||||
eprintln!($($arg)*);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
|
||||||
use cli::Args;
|
use cli::Args;
|
||||||
use config::Config;
|
use ecr::chroot;
|
||||||
use distro::{
|
use ecr::config::Config;
|
||||||
|
use ecr::distro::{
|
||||||
map_arch, parse_image_ref, resolve_distro_url, resolve_distro_version, Distro, ImageSource,
|
map_arch, parse_image_ref, resolve_distro_url, resolve_distro_version, Distro, ImageSource,
|
||||||
};
|
};
|
||||||
use download::{digest_sidecar, download_image, fetch_oci_digest};
|
use ecr::download::{digest_sidecar, download_image, fetch_oci_digest};
|
||||||
use extract::extract_tarball;
|
use ecr::extract::extract_tarball;
|
||||||
|
use ecr::{kernel, mount, namespace, qemu, qemu_vm, utils, veprintln, verbose};
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
let args = Args::parse();
|
let args = Args::parse();
|
||||||
@@ -227,7 +207,7 @@ fn namespace_mode(args: Args, rootfs: std::path::PathBuf, config: Config) -> Res
|
|||||||
&rootfs_clone,
|
&rootfs_clone,
|
||||||
&bind_paths_clone,
|
&bind_paths_clone,
|
||||||
&bind_rw_paths_clone,
|
&bind_rw_paths_clone,
|
||||||
&args_clone,
|
args_clone.no_bind,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// Write resolv.conf with DNS from config
|
// Write resolv.conf with DNS from config
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
[package]
|
||||||
|
name = "ecr"
|
||||||
|
description = "Ephemeral chroot environments with Linux namespaces"
|
||||||
|
version.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
rust-version.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
authors.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
# Config parsing
|
||||||
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
serde_yaml = "0.9"
|
||||||
|
|
||||||
|
# HTTP downloads
|
||||||
|
reqwest = { version = "0.13", features = ["blocking", "stream"] }
|
||||||
|
|
||||||
|
# Tarball extraction
|
||||||
|
tar = "0.4"
|
||||||
|
flate2 = "1"
|
||||||
|
xz2 = "0.1"
|
||||||
|
zstd = "0.13"
|
||||||
|
|
||||||
|
# Unix syscall bindings
|
||||||
|
nix = { version = "0.31", features = ["fs", "mount", "sched", "signal", "user", "process", "hostname"] }
|
||||||
|
|
||||||
|
# Temp directories
|
||||||
|
tempfile = "3"
|
||||||
|
|
||||||
|
# Error handling
|
||||||
|
anyhow = "1"
|
||||||
|
|
||||||
|
# Utilities
|
||||||
|
dirs = "6"
|
||||||
|
which = "7"
|
||||||
|
cpio = "0.4"
|
||||||
|
base64 = "0.22"
|
||||||
|
tokio = { version = "1", features = ["rt-multi-thread", "macros", "io-util"] }
|
||||||
|
futures-util = "0.3"
|
||||||
|
indicatif = "0.18"
|
||||||
|
serde_json = "1"
|
||||||
|
libc = "0.2"
|
||||||
|
users = "0.11"
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
//! ecr — ephemeral chroot environments with Linux namespaces.
|
||||||
|
//!
|
||||||
|
//! This crate is the library behind the `ecr` CLI. It resolves distro and
|
||||||
|
//! OCI image references, downloads them through a content cache, extracts
|
||||||
|
//! the rootfs into a scratch directory, and runs commands inside
|
||||||
|
//! unprivileged user/PID/mount/UTS namespaces — or boots it in a QEMU VM
|
||||||
|
//! (see the `qemu_vm` module).
|
||||||
|
|
||||||
|
pub mod chroot;
|
||||||
|
pub mod config;
|
||||||
|
pub mod distro;
|
||||||
|
pub mod download;
|
||||||
|
pub mod extract;
|
||||||
|
pub mod kernel;
|
||||||
|
pub mod mount;
|
||||||
|
pub mod namespace;
|
||||||
|
pub mod qemu;
|
||||||
|
pub mod qemu_vm;
|
||||||
|
pub mod utils;
|
||||||
|
pub mod verbose;
|
||||||
|
|
||||||
|
/// Print to stderr only when verbose mode is active (see [`verbose::set`]).
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! veprintln {
|
||||||
|
($($arg:tt)*) => {
|
||||||
|
if $crate::verbose::is_verbose() {
|
||||||
|
eprintln!($($arg)*);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -21,15 +21,13 @@ fn escape_overlay_path(path: &Path) -> Result<String> {
|
|||||||
Ok(s.replace('\\', "\\\\").replace(',', "\\,"))
|
Ok(s.replace('\\', "\\\\").replace(',', "\\,"))
|
||||||
}
|
}
|
||||||
|
|
||||||
use crate::cli::Args;
|
|
||||||
|
|
||||||
/// Setup all required mounts inside the chroot
|
/// Setup all required mounts inside the chroot
|
||||||
/// Returns a TempDir that must be kept alive for the duration of the chroot
|
/// Returns a TempDir that must be kept alive for the duration of the chroot
|
||||||
pub fn setup_mounts(
|
pub fn setup_mounts(
|
||||||
rootfs: &Path,
|
rootfs: &Path,
|
||||||
bind_paths: &[std::path::PathBuf],
|
bind_paths: &[std::path::PathBuf],
|
||||||
bind_rw_paths: &[std::path::PathBuf],
|
bind_rw_paths: &[std::path::PathBuf],
|
||||||
args: &Args,
|
no_bind: bool,
|
||||||
) -> Result<Vec<TempDir>> {
|
) -> Result<Vec<TempDir>> {
|
||||||
// Keep all overlay temp dirs alive
|
// Keep all overlay temp dirs alive
|
||||||
let mut overlay_temps: Vec<TempDir> = Vec::new();
|
let mut overlay_temps: Vec<TempDir> = Vec::new();
|
||||||
@@ -60,7 +58,7 @@ pub fn setup_mounts(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Setup overlay mounts for bind paths (read-only via overlay)
|
// Setup overlay mounts for bind paths (read-only via overlay)
|
||||||
if !args.no_bind {
|
if !no_bind {
|
||||||
for bind_path in bind_paths {
|
for bind_path in bind_paths {
|
||||||
// Skip if this path is also in bind_rw (bind_rw takes precedence)
|
// Skip if this path is also in bind_rw (bind_rw takes precedence)
|
||||||
if !bind_rw_paths.contains(bind_path) {
|
if !bind_rw_paths.contains(bind_path) {
|
||||||
@@ -42,6 +42,9 @@ pub enum Arch {
|
|||||||
|
|
||||||
impl Arch {
|
impl Arch {
|
||||||
/// Get the architecture from a string (any common naming convention)
|
/// Get the architecture from a string (any common naming convention)
|
||||||
|
/// Not a FromStr impl: unrecognized names map to Arch::Unknown rather
|
||||||
|
/// than an error, by design.
|
||||||
|
#[allow(clippy::should_implement_trait)]
|
||||||
pub fn from_str(s: &str) -> Self {
|
pub fn from_str(s: &str) -> Self {
|
||||||
match s {
|
match s {
|
||||||
"amd64" | "x86_64" | "x64" => Arch::Amd64,
|
"amd64" | "x86_64" | "x64" => Arch::Amd64,
|
||||||
Reference in New Issue
Block a user