summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/hasher.rs95
-rw-r--r--src/lib.rs2
-rw-r--r--src/package_installer.rs91
3 files changed, 188 insertions, 0 deletions
diff --git a/src/hasher.rs b/src/hasher.rs
new file mode 100644
index 0000000..0be1808
--- /dev/null
+++ b/src/hasher.rs
@@ -0,0 +1,95 @@
+use std::ffi::OsString;
+use std::process::Command;
+
+use anyhow::{bail, Context, Result};
+
+use crate::package_installer::{PackageInstaller, PackageRequest};
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct Invocation {
+ program: OsString,
+ arguments: Vec<OsString>,
+}
+
+impl Invocation {
+ pub fn new(
+ program: impl Into<OsString>,
+ arguments: impl IntoIterator<Item = impl Into<OsString>>,
+ ) -> Self {
+ Self {
+ program: program.into(),
+ arguments: arguments.into_iter().map(Into::into).collect(),
+ }
+ }
+
+ pub fn program(&self) -> &OsString {
+ &self.program
+ }
+
+ pub fn arguments(&self) -> &[OsString] {
+ &self.arguments
+ }
+}
+
+pub trait CommandRunner {
+ fn run(&self, invocation: Invocation) -> Result<()>;
+}
+
+#[derive(Debug, Default, Clone, Copy)]
+pub struct ProcessRunner;
+
+impl CommandRunner for ProcessRunner {
+ fn run(&self, invocation: Invocation) -> Result<()> {
+ let status = Command::new(&invocation.program)
+ .args(&invocation.arguments)
+ .status()
+ .with_context(|| format!("run {}", invocation.program.to_string_lossy()))?;
+ if !status.success() {
+ bail!(
+ "{} exited with {status}",
+ invocation.program.to_string_lossy()
+ );
+ }
+ Ok(())
+ }
+}
+
+#[derive(Debug)]
+pub struct HasherInstaller<R> {
+ runner: R,
+}
+
+impl<R> HasherInstaller<R> {
+ pub fn new(runner: R) -> Self {
+ Self { runner }
+ }
+
+ pub fn runner(&self) -> &R {
+ &self.runner
+ }
+}
+
+impl<R: CommandRunner> PackageInstaller for HasherInstaller<R> {
+ fn install(&self, request: &PackageRequest) -> Result<()> {
+ self.runner.run(Invocation::new(
+ "hsh",
+ [
+ "--mountpoints=/proc".into(),
+ "--initroot-only".into(),
+ "--apt-config".into(),
+ request.apt_config().as_path().as_os_str().to_owned(),
+ "--workdir".into(),
+ request.workdir().as_os_str().to_owned(),
+ ],
+ ))?;
+
+ let mut arguments = vec![
+ OsString::from("--mountpoints=/proc"),
+ OsString::from("--workdir"),
+ request.workdir().as_os_str().to_owned(),
+ ];
+ arguments.extend(request.selectors().iter().map(OsString::from));
+ arguments.extend(request.packages().iter().map(OsString::from));
+ self.runner.run(Invocation::new("hsh-install", arguments))
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
index 8016607..cfcc645 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,6 +1,8 @@
pub mod cli;
pub mod files;
+pub mod hasher;
pub mod model;
+pub mod package_installer;
pub mod plan;
pub mod rootfs;
pub mod stage;
diff --git a/src/package_installer.rs b/src/package_installer.rs
new file mode 100644
index 0000000..b08c469
--- /dev/null
+++ b/src/package_installer.rs
@@ -0,0 +1,91 @@
+use std::path::{Path, PathBuf};
+
+use anyhow::{bail, Result};
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct AptConfig(PathBuf);
+
+impl AptConfig {
+ pub fn new(path: impl AsRef<Path>) -> Result<Self> {
+ let path = path.as_ref();
+ if path.as_os_str().is_empty() || path.is_dir() {
+ bail!("APT configuration must be a file path: {}", path.display());
+ }
+ Ok(Self(path.to_path_buf()))
+ }
+
+ pub fn as_path(&self) -> &Path {
+ &self.0
+ }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct PackageRequest {
+ workdir: PathBuf,
+ apt_config: AptConfig,
+ packages: Vec<String>,
+ selectors: Vec<String>,
+}
+
+impl PackageRequest {
+ pub fn new(
+ workdir: impl AsRef<Path>,
+ apt_config: AptConfig,
+ packages: impl IntoIterator<Item = impl Into<String>>,
+ selectors: impl IntoIterator<Item = impl Into<String>>,
+ ) -> Result<Self> {
+ let workdir = workdir.as_ref();
+ if workdir.as_os_str().is_empty() {
+ bail!("Hasher workdir cannot be empty");
+ }
+
+ let mut packages = normalize("package", packages)?;
+ let mut selectors = normalize("package selector", selectors)?;
+ packages.sort();
+ packages.dedup();
+ selectors.sort();
+ selectors.dedup();
+ Ok(Self {
+ workdir: workdir.to_path_buf(),
+ apt_config,
+ packages,
+ selectors,
+ })
+ }
+
+ pub fn workdir(&self) -> &Path {
+ &self.workdir
+ }
+
+ pub fn apt_config(&self) -> &AptConfig {
+ &self.apt_config
+ }
+
+ pub fn packages(&self) -> &[String] {
+ &self.packages
+ }
+
+ pub fn selectors(&self) -> &[String] {
+ &self.selectors
+ }
+}
+
+fn normalize(
+ kind: &str,
+ values: impl IntoIterator<Item = impl Into<String>>,
+) -> Result<Vec<String>> {
+ values
+ .into_iter()
+ .map(Into::into)
+ .map(|value: String| {
+ if value.trim().is_empty() {
+ bail!("{kind} cannot be empty");
+ }
+ Ok(value)
+ })
+ .collect()
+}
+
+pub trait PackageInstaller {
+ fn install(&self, request: &PackageRequest) -> Result<()>;
+}