summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/archive.rs34
-rw-r--r--src/build.rs34
-rw-r--r--src/cli.rs13
-rw-r--r--src/compare.rs105
-rw-r--r--src/files.rs7
-rw-r--r--src/hasher.rs2
-rw-r--r--src/initramfs.rs45
-rw-r--r--src/lib.rs4
-rw-r--r--src/main.rs2
-rw-r--r--src/manifest.rs202
-rw-r--r--src/model.rs8
-rw-r--r--src/package_installer.rs7
-rw-r--r--src/rootfs.rs34
-rw-r--r--tests/archive.rs47
-rw-r--r--tests/build.rs71
-rw-r--r--tests/cli.rs37
-rw-r--r--tests/compare.rs306
-rw-r--r--tests/initramfs.rs28
-rw-r--r--tests/model_validation.rs5
-rw-r--r--tests/package_installer.rs34
-rw-r--r--tests/rootfs_finalization.rs63
-rw-r--r--tests/stage_graph.rs5
22 files changed, 808 insertions, 285 deletions
diff --git a/src/archive.rs b/src/archive.rs
index c0a2195..fd4d899 100644
--- a/src/archive.rs
+++ b/src/archive.rs
@@ -3,7 +3,7 @@ use std::fs::{self, File, OpenOptions};
use std::os::unix::fs::MetadataExt;
use std::path::{Path, PathBuf};
-use anyhow::{bail, Context, Result};
+use anyhow::{Context, Result, bail};
use tar::{Builder, EntryType, Header};
/// Writes a deterministic rootfs tar archive using numeric ownership metadata.
@@ -28,7 +28,10 @@ impl NativeTarWriter {
let output = output.as_ref();
reject_output_inside_rootfs(rootfs, output)?;
if output.exists() {
- bail!("archive output already exists; refusing to overwrite it: {}", output.display());
+ bail!(
+ "archive output already exists; refusing to overwrite it: {}",
+ output.display()
+ );
}
let temporary = temporary_output_path(output)?;
let result = (|| {
@@ -67,9 +70,12 @@ fn reject_output_inside_rootfs(rootfs: &Path, output: &Path) -> Result<()> {
.canonicalize()
.with_context(|| format!("canonicalize rootfs {}", rootfs.display()))?;
let output_parent = output.parent().unwrap_or_else(|| Path::new("."));
- let output_parent = output_parent
- .canonicalize()
- .with_context(|| format!("canonicalize archive output parent {}", output_parent.display()))?;
+ let output_parent = output_parent.canonicalize().with_context(|| {
+ format!(
+ "canonicalize archive output parent {}",
+ output_parent.display()
+ )
+ })?;
if output_parent.starts_with(&rootfs) {
bail!(
"archive output must not be inside rootfs: {} is below {}",
@@ -81,9 +87,9 @@ fn reject_output_inside_rootfs(rootfs: &Path, output: &Path) -> Result<()> {
}
fn temporary_output_path(output: &Path) -> Result<PathBuf> {
- let name = output
- .file_name()
- .ok_or_else(|| anyhow::anyhow!("archive output path has no filename: {}", output.display()))?;
+ let name = output.file_name().ok_or_else(|| {
+ anyhow::anyhow!("archive output path has no filename: {}", output.display())
+ })?;
let mut temporary_name = name.to_os_string();
temporary_name.push(".partial");
Ok(output.with_file_name(temporary_name))
@@ -139,7 +145,11 @@ fn deterministic_header(metadata: &fs::Metadata, entry_type: EntryType, size: u6
header
}
-fn append_directory(archive: &mut Builder<File>, path: &Path, metadata: &fs::Metadata) -> Result<()> {
+fn append_directory(
+ archive: &mut Builder<File>,
+ path: &Path,
+ metadata: &fs::Metadata,
+) -> Result<()> {
let mut header = deterministic_header(metadata, EntryType::Directory, 0);
archive
.append_data(&mut header, path, std::io::empty())
@@ -158,7 +168,8 @@ fn append_file(
return append_hardlink(archive, path, target, metadata);
}
let mut header = deterministic_header(metadata, EntryType::Regular, metadata.len());
- let input = File::open(source).with_context(|| format!("open rootfs file {}", source.display()))?;
+ let input =
+ File::open(source).with_context(|| format!("open rootfs file {}", source.display()))?;
archive
.append_data(&mut header, path, input)
.with_context(|| format!("append file {}", path.display()))?;
@@ -184,7 +195,8 @@ fn append_symlink(
path: &Path,
metadata: &fs::Metadata,
) -> Result<()> {
- let target = fs::read_link(source).with_context(|| format!("read rootfs symlink {}", source.display()))?;
+ let target = fs::read_link(source)
+ .with_context(|| format!("read rootfs symlink {}", source.display()))?;
let mut header = deterministic_header(metadata, EntryType::Symlink, 0);
archive
.append_link(&mut header, path, target)
diff --git a/src/build.rs b/src/build.rs
index 1fd7720..7fd3989 100644
--- a/src/build.rs
+++ b/src/build.rs
@@ -1,13 +1,13 @@
use std::path::{Path, PathBuf};
-use anyhow::{bail, Context, Result};
+use anyhow::{Context, Result, bail};
+use crate::BuildPlan;
use crate::archive::NativeTarWriter;
use crate::initramfs::{InitramfsBuilder, InitramfsRequest, InitramfsResult};
use crate::manifest::{ArtifactManifest, InitrdRecord};
use crate::package_installer::PackageInstaller;
use crate::rootfs::{InitrdOem, RootfsFinalization};
-use crate::BuildPlan;
/// Executes the native stages after a plan has been validated.
///
@@ -22,7 +22,11 @@ pub struct BuildExecutor<I, B> {
impl<I, B> BuildExecutor<I, B> {
pub fn new(installer: I, initramfs_builder: B) -> Self {
- Self { installer, initramfs_builder, tar_writer: NativeTarWriter::new() }
+ Self {
+ installer,
+ initramfs_builder,
+ tar_writer: NativeTarWriter::new(),
+ }
}
}
@@ -36,20 +40,31 @@ impl<I: PackageInstaller, B: InitramfsBuilder> BuildExecutor<I, B> {
) -> Result<BuildResult> {
let workspace = workspace.as_ref();
if workspace.exists() {
- bail!("workspace already exists; refusing to reuse it: {}", workspace.display());
+ bail!(
+ "workspace already exists; refusing to reuse it: {}",
+ workspace.display()
+ );
}
let artifact = artifact.as_ref();
if artifact.exists() {
- bail!("output artifact already exists; refusing to overwrite it: {}", artifact.display());
+ bail!(
+ "output artifact already exists; refusing to overwrite it: {}",
+ artifact.display()
+ );
}
let manifest = ArtifactManifest::path_beside(artifact)?;
if manifest.exists() {
- bail!("output manifest already exists; refusing to overwrite it: {}", manifest.display());
+ bail!(
+ "output manifest already exists; refusing to overwrite it: {}",
+ manifest.display()
+ );
}
let parent = artifact
.parent()
.filter(|parent| !parent.as_os_str().is_empty())
- .ok_or_else(|| anyhow::anyhow!("artifact path has no parent: {}", artifact.display()))?;
+ .ok_or_else(|| {
+ anyhow::anyhow!("artifact path has no parent: {}", artifact.display())
+ })?;
if parent.exists() && !parent.is_dir() {
bail!("artifact parent is not a directory: {}", parent.display());
}
@@ -67,7 +82,10 @@ impl<I: PackageInstaller, B: InitramfsBuilder> BuildExecutor<I, B> {
if !rootfs.is_dir() {
return Self::fail_and_remove_workspace(
workspace,
- anyhow::anyhow!("package installer did not create rootfs: {}", rootfs.display()),
+ anyhow::anyhow!(
+ "package installer did not create rootfs: {}",
+ rootfs.display()
+ ),
);
}
if let Err(error) = RootfsFinalization::new(
diff --git a/src/cli.rs b/src/cli.rs
index f34f485..ffa9bba 100644
--- a/src/cli.rs
+++ b/src/cli.rs
@@ -1,6 +1,6 @@
use std::path::PathBuf;
-use anyhow::{bail, Result};
+use anyhow::{Result, bail};
use clap::{Parser, Subcommand};
use crate::build::BuildExecutor;
@@ -77,7 +77,11 @@ pub fn run(cli: Cli) -> Result<()> {
println!("artifact: {}", result.artifact().display());
println!("manifest: {}", result.manifest().display());
if let Some(initrd) = result.initrd() {
- println!("initrd: {} sha256:{}", initrd.initrd_path().display(), initrd.sha256());
+ println!(
+ "initrd: {} sha256:{}",
+ initrd.initrd_path().display(),
+ initrd.sha256()
+ );
}
Ok(())
}
@@ -101,7 +105,10 @@ pub fn run(cli: Cli) -> Result<()> {
}
fn load_artifact(path: &std::path::Path) -> Result<ArtifactManifest> {
- if matches!(path.extension().and_then(|extension| extension.to_str()), Some("toml")) {
+ if matches!(
+ path.extension().and_then(|extension| extension.to_str()),
+ Some("toml")
+ ) {
return ArtifactManifest::load(path);
}
diff --git a/src/compare.rs b/src/compare.rs
index 8b59c1c..3382d95 100644
--- a/src/compare.rs
+++ b/src/compare.rs
@@ -50,11 +50,21 @@ impl ComparisonReport {
let mut output = String::new();
for difference in &self.differences {
match difference {
- SemanticDifference::Package { name, change } => writeln!(output, "{} package {name}", change.verb()),
- SemanticDifference::File { path, change } => writeln!(output, "{} file {path}", change.verb()),
- SemanticDifference::Initrd { change } => writeln!(output, "{} initrd", change.verb()),
- SemanticDifference::Service { name, change } => writeln!(output, "{} service {name}", change.verb()),
- SemanticDifference::ArchiveMember { path, change } => writeln!(output, "{} archive member {path}", change.verb()),
+ SemanticDifference::Package { name, change } => {
+ writeln!(output, "{} package {name}", change.verb())
+ }
+ SemanticDifference::File { path, change } => {
+ writeln!(output, "{} file {path}", change.verb())
+ }
+ SemanticDifference::Initrd { change } => {
+ writeln!(output, "{} initrd", change.verb())
+ }
+ SemanticDifference::Service { name, change } => {
+ writeln!(output, "{} service {name}", change.verb())
+ }
+ SemanticDifference::ArchiveMember { path, change } => {
+ writeln!(output, "{} archive member {path}", change.verb())
+ }
}
.expect("writing to String cannot fail");
}
@@ -64,26 +74,60 @@ impl ComparisonReport {
pub fn compare(left: &ArtifactManifest, right: &ArtifactManifest) -> ComparisonReport {
let mut differences = Vec::new();
- compare_records(&left.packages, &right.packages, |record| &record.name, |name, change| {
- SemanticDifference::Package { name: name.to_owned(), change }
- }, &mut differences);
- compare_records(&left.files, &right.files, |record| &record.path, |path, change| {
- SemanticDifference::File { path: path.to_owned(), change }
- }, &mut differences);
+ compare_records(
+ &left.packages,
+ &right.packages,
+ |record| &record.name,
+ |name, change| SemanticDifference::Package {
+ name: name.to_owned(),
+ change,
+ },
+ &mut differences,
+ );
+ compare_records(
+ &left.files,
+ &right.files,
+ |record| &record.path,
+ |path, change| SemanticDifference::File {
+ path: path.to_owned(),
+ change,
+ },
+ &mut differences,
+ );
match (&left.initrd, &right.initrd) {
- (None, Some(_)) => differences.push(SemanticDifference::Initrd { change: Change::Added }),
- (Some(_), None) => differences.push(SemanticDifference::Initrd { change: Change::Removed }),
+ (None, Some(_)) => differences.push(SemanticDifference::Initrd {
+ change: Change::Added,
+ }),
+ (Some(_), None) => differences.push(SemanticDifference::Initrd {
+ change: Change::Removed,
+ }),
(Some(left), Some(right)) if left != right => {
- differences.push(SemanticDifference::Initrd { change: Change::Changed });
+ differences.push(SemanticDifference::Initrd {
+ change: Change::Changed,
+ });
}
_ => {}
}
- compare_records(&left.services, &right.services, |record| &record.name, |name, change| {
- SemanticDifference::Service { name: name.to_owned(), change }
- }, &mut differences);
- compare_records(&left.archive_members, &right.archive_members, |record| &record.path, |path, change| {
- SemanticDifference::ArchiveMember { path: path.to_owned(), change }
- }, &mut differences);
+ compare_records(
+ &left.services,
+ &right.services,
+ |record| &record.name,
+ |name, change| SemanticDifference::Service {
+ name: name.to_owned(),
+ change,
+ },
+ &mut differences,
+ );
+ compare_records(
+ &left.archive_members,
+ &right.archive_members,
+ |record| &record.path,
+ |path, change| SemanticDifference::ArchiveMember {
+ path: path.to_owned(),
+ change,
+ },
+ &mut differences,
+ );
differences.sort();
ComparisonReport { differences }
}
@@ -95,13 +139,26 @@ fn compare_records<T: PartialEq>(
difference: impl Fn(&str, Change) -> SemanticDifference,
output: &mut Vec<SemanticDifference>,
) {
- let left = left.iter().map(|record| (key(record), record)).collect::<BTreeMap<_, _>>();
- let right = right.iter().map(|record| (key(record), record)).collect::<BTreeMap<_, _>>();
- for name in left.keys().chain(right.keys()).copied().collect::<std::collections::BTreeSet<_>>() {
+ let left = left
+ .iter()
+ .map(|record| (key(record), record))
+ .collect::<BTreeMap<_, _>>();
+ let right = right
+ .iter()
+ .map(|record| (key(record), record))
+ .collect::<BTreeMap<_, _>>();
+ for name in left
+ .keys()
+ .chain(right.keys())
+ .copied()
+ .collect::<std::collections::BTreeSet<_>>()
+ {
match (left.get(name), right.get(name)) {
(None, Some(_)) => output.push(difference(name, Change::Added)),
(Some(_), None) => output.push(difference(name, Change::Removed)),
- (Some(left), Some(right)) if left != right => output.push(difference(name, Change::Changed)),
+ (Some(left), Some(right)) if left != right => {
+ output.push(difference(name, Change::Changed))
+ }
_ => {}
}
}
diff --git a/src/files.rs b/src/files.rs
index f49bd5e..97521f8 100644
--- a/src/files.rs
+++ b/src/files.rs
@@ -1,6 +1,6 @@
use std::path::{Component, Path, PathBuf};
-use anyhow::{bail, Result};
+use anyhow::{Result, bail};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RootfsPath(PathBuf);
@@ -9,7 +9,10 @@ impl RootfsPath {
pub fn new(path: impl AsRef<Path>) -> Result<Self> {
let path = path.as_ref();
if path.as_os_str().is_empty() || path.is_absolute() {
- bail!("rootfs path must be a non-empty relative path: {}", path.display());
+ bail!(
+ "rootfs path must be a non-empty relative path: {}",
+ path.display()
+ );
}
let mut normalized = PathBuf::new();
diff --git a/src/hasher.rs b/src/hasher.rs
index 0be1808..da00778 100644
--- a/src/hasher.rs
+++ b/src/hasher.rs
@@ -1,7 +1,7 @@
use std::ffi::OsString;
use std::process::Command;
-use anyhow::{bail, Context, Result};
+use anyhow::{Context, Result, bail};
use crate::package_installer::{PackageInstaller, PackageRequest};
diff --git a/src/initramfs.rs b/src/initramfs.rs
index 4475aed..a108b85 100644
--- a/src/initramfs.rs
+++ b/src/initramfs.rs
@@ -4,7 +4,7 @@ use std::os::unix::fs::symlink;
use std::path::{Path, PathBuf};
use std::process::Command;
-use anyhow::{bail, Context, Result};
+use anyhow::{Context, Result, bail};
use sha2::{Digest, Sha256};
#[derive(Debug, Clone, PartialEq, Eq)]
@@ -32,7 +32,11 @@ impl KernelVersion {
match kernels.as_slice() {
[] => bail!("no RT kernel image found in {}", boot.display()),
[version] => Ok(Self(version.clone())),
- _ => bail!("multiple RT kernel images found in {}: {}", boot.display(), kernels.join(", ")),
+ _ => bail!(
+ "multiple RT kernel images found in {}: {}",
+ boot.display(),
+ kernels.join(", ")
+ ),
}
}
@@ -70,7 +74,10 @@ impl InitramfsRecipe {
}
}
-fn normalize(kind: &str, values: impl IntoIterator<Item = impl Into<String>>) -> Result<Vec<String>> {
+fn normalize(
+ kind: &str,
+ values: impl IntoIterator<Item = impl Into<String>>,
+) -> Result<Vec<String>> {
values
.into_iter()
.map(Into::into)
@@ -117,8 +124,14 @@ pub struct Invocation {
}
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 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(),
+ }
}
}
@@ -136,7 +149,10 @@ impl CommandRunner for ProcessRunner {
.status()
.with_context(|| format!("run {}", invocation.program.to_string_lossy()))?;
if !status.success() {
- bail!("{} exited with {status}", invocation.program.to_string_lossy());
+ bail!(
+ "{} exited with {status}",
+ invocation.program.to_string_lossy()
+ );
}
Ok(())
}
@@ -193,11 +209,17 @@ fn write_boot_alias(rootfs: &Path, alias: &str, target: &Path) -> Result<()> {
let path = rootfs.join("boot").join(alias);
match fs::symlink_metadata(&path) {
Ok(metadata) if metadata.file_type().is_symlink() => {
- fs::remove_file(&path).with_context(|| format!("remove existing boot alias {}", path.display()))?;
+ fs::remove_file(&path)
+ .with_context(|| format!("remove existing boot alias {}", path.display()))?;
}
- Ok(_) => bail!("refusing to replace non-symlink boot alias: {}", path.display()),
+ Ok(_) => bail!(
+ "refusing to replace non-symlink boot alias: {}",
+ path.display()
+ ),
Err(error) if error.kind() == std::io::ErrorKind::NotFound => {}
- Err(error) => return Err(error).with_context(|| format!("inspect boot alias {}", path.display())),
+ Err(error) => {
+ return Err(error).with_context(|| format!("inspect boot alias {}", path.display()));
+ }
}
symlink(target, &path).with_context(|| format!("create boot alias {}", path.display()))
}
@@ -214,7 +236,10 @@ impl InitramfsResult {
let initrd_path = PathBuf::from(format!("boot/initrd-{}.img", kernel.as_str()));
let contents = fs::read(rootfs.join(&initrd_path))
.with_context(|| format!("read generated initrd {}", initrd_path.display()))?;
- Ok(Self { initrd_path, sha256: format!("{:x}", Sha256::digest(contents)) })
+ Ok(Self {
+ initrd_path,
+ sha256: format!("{:x}", Sha256::digest(contents)),
+ })
}
pub fn initrd_path(&self) -> &Path {
diff --git a/src/lib.rs b/src/lib.rs
index d896ac6..0ec354d 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -12,6 +12,8 @@ pub mod plan;
pub mod rootfs;
pub mod stage;
-pub use model::{Architecture, BootSpec, ImageSpec, OutputFormat, PackageSelector, PackageSpec, Target};
+pub use model::{
+ Architecture, BootSpec, ImageSpec, OutputFormat, PackageSelector, PackageSpec, Target,
+};
pub use plan::BuildPlan;
pub use stage::Stage;
diff --git a/src/main.rs b/src/main.rs
index 1e06263..3ae6b3e 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,4 +1,4 @@
-use alt_controller_image::cli::{run, Cli};
+use alt_controller_image::cli::{Cli, run};
use clap::Parser;
fn main() {
diff --git a/src/manifest.rs b/src/manifest.rs
index 067dc9a..0821fab 100644
--- a/src/manifest.rs
+++ b/src/manifest.rs
@@ -4,7 +4,7 @@ use std::io::{Read, Write};
use std::os::unix::fs::MetadataExt;
use std::path::{Path, PathBuf};
-use anyhow::{bail, Context, Result};
+use anyhow::{Context, Result, bail};
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use tar::{Archive, EntryType};
@@ -34,18 +34,41 @@ impl ArtifactManifest {
validate_file_records(&files)?;
validate_initrd_record(initrd.as_ref())?;
validate_manifest_paths(&files, initrd.as_ref(), &archive_members)?;
- ensure_unique("package", packages.iter().map(|record| record.name.as_str()))?;
+ ensure_unique(
+ "package",
+ packages.iter().map(|record| record.name.as_str()),
+ )?;
ensure_unique("file", files.iter().map(|record| record.path.as_str()))?;
- ensure_unique("service", services.iter().map(|record| record.name.as_str()))?;
- ensure_unique("archive member", archive_members.iter().map(|record| record.path.as_str()))?;
- Ok(Self { packages, files, initrd, services, archive_members })
+ ensure_unique(
+ "service",
+ services.iter().map(|record| record.name.as_str()),
+ )?;
+ ensure_unique(
+ "archive member",
+ archive_members.iter().map(|record| record.path.as_str()),
+ )?;
+ Ok(Self {
+ packages,
+ files,
+ initrd,
+ services,
+ archive_members,
+ })
}
pub fn load(path: impl AsRef<Path>) -> Result<Self> {
let path = path.as_ref();
- let text = fs::read_to_string(path).with_context(|| format!("read artifact manifest {}", path.display()))?;
- let manifest: Self = toml::from_str(&text).with_context(|| format!("parse artifact manifest {}", path.display()))?;
- Self::new(manifest.packages, manifest.files, manifest.initrd, manifest.services, manifest.archive_members)
+ let text = fs::read_to_string(path)
+ .with_context(|| format!("read artifact manifest {}", path.display()))?;
+ let manifest: Self = toml::from_str(&text)
+ .with_context(|| format!("parse artifact manifest {}", path.display()))?;
+ Self::new(
+ manifest.packages,
+ manifest.files,
+ manifest.initrd,
+ manifest.services,
+ manifest.archive_members,
+ )
}
pub fn write_beside(&self, artifact: impl AsRef<Path>) -> Result<PathBuf> {
@@ -67,9 +90,9 @@ impl ArtifactManifest {
/// directory.
pub fn path_beside(artifact: impl AsRef<Path>) -> Result<PathBuf> {
let artifact = artifact.as_ref();
- let name = artifact
- .file_name()
- .ok_or_else(|| anyhow::anyhow!("artifact path has no filename: {}", artifact.display()))?;
+ let name = artifact.file_name().ok_or_else(|| {
+ anyhow::anyhow!("artifact path has no filename: {}", artifact.display())
+ })?;
let mut manifest_name = name.to_os_string();
manifest_name.push(".manifest.toml");
Ok(artifact.with_file_name(manifest_name))
@@ -134,7 +157,10 @@ impl ArtifactManifest {
EntryType::Directory => "directory",
EntryType::Symlink => "symlink",
EntryType::Link => "hardlink",
- other => bail!("unsupported archive member type {other:?} in {}", archive_path.display()),
+ other => bail!(
+ "unsupported archive member type {other:?} in {}",
+ archive_path.display()
+ ),
};
archive_members.push(ArchiveMemberRecord::new(path.clone(), kind));
@@ -144,7 +170,10 @@ impl ArtifactManifest {
if path.starts_with("boot/initrd-") && path.ends_with(".img") {
let record = InitrdRecord::new(path.clone(), digest.clone());
if initrd.replace(record).is_some() {
- bail!("multiple initrd images found in archive {}", archive_path.display());
+ bail!(
+ "multiple initrd images found in archive {}",
+ archive_path.display()
+ );
}
}
if !path.starts_with("boot/initrd-") || !path.ends_with(".img") {
@@ -194,7 +223,10 @@ fn collect_files(rootfs: &Path, initrd: Option<&InitrdRecord>) -> Result<Vec<Fil
entries.sort_by_key(|entry| entry.path().to_path_buf());
let mut hardlink_targets: BTreeMap<(u64, u64), String> = BTreeMap::new();
for entry in entries {
- let relative = entry.path().strip_prefix(rootfs).expect("walk entry is below rootfs");
+ let relative = entry
+ .path()
+ .strip_prefix(rootfs)
+ .expect("walk entry is below rootfs");
if is_internal_metadata_path(&portable_path(relative)?) {
continue;
}
@@ -204,7 +236,9 @@ fn collect_files(rootfs: &Path, initrd: Option<&InitrdRecord>) -> Result<Vec<Fil
}
let file_type = entry.file_type();
if file_type.is_file() {
- let metadata = entry.metadata().with_context(|| format!("inspect rootfs file {}", entry.path().display()))?;
+ let metadata = entry
+ .metadata()
+ .with_context(|| format!("inspect rootfs file {}", entry.path().display()))?;
let key = (metadata.dev(), metadata.ino());
if let Some(target) = hardlink_targets.get(&key) {
files.push(FileRecord::hardlink(path, target.clone()));
@@ -230,7 +264,8 @@ fn collect_services(rootfs: &Path) -> Result<Vec<ServiceRecord>> {
}
let mut services = Vec::new();
for entry in WalkDir::new(&system).follow_links(false).min_depth(2) {
- let entry = entry.with_context(|| format!("walk service state directory {}", system.display()))?;
+ let entry =
+ entry.with_context(|| format!("walk service state directory {}", system.display()))?;
let parent_is_wants_directory = entry
.path()
.parent()
@@ -239,10 +274,9 @@ fn collect_services(rootfs: &Path) -> Result<Vec<ServiceRecord>> {
if !parent_is_wants_directory || !entry.file_type().is_symlink() {
continue;
}
- let name = entry
- .file_name()
- .to_str()
- .ok_or_else(|| anyhow::anyhow!("non-UTF-8 service name in {}", entry.path().display()))?;
+ let name = entry.file_name().to_str().ok_or_else(|| {
+ anyhow::anyhow!("non-UTF-8 service name in {}", entry.path().display())
+ })?;
if name.ends_with(".service") {
services.push(ServiceRecord::new(name, true));
}
@@ -257,11 +291,16 @@ fn coalesce_enabled_services(mut services: Vec<ServiceRecord>) -> Vec<ServiceRec
}
fn collect_archive_members(archive_path: &Path) -> Result<Vec<ArchiveMemberRecord>> {
- let file = fs::File::open(archive_path).with_context(|| format!("open archive {}", archive_path.display()))?;
+ let file = fs::File::open(archive_path)
+ .with_context(|| format!("open archive {}", archive_path.display()))?;
let mut archive = Archive::new(file);
let mut members = Vec::new();
- for entry in archive.entries().with_context(|| format!("read archive {}", archive_path.display()))? {
- let entry = entry.with_context(|| format!("read archive member from {}", archive_path.display()))?;
+ for entry in archive
+ .entries()
+ .with_context(|| format!("read archive {}", archive_path.display()))?
+ {
+ let entry = entry
+ .with_context(|| format!("read archive member from {}", archive_path.display()))?;
let path = portable_path(&entry.path()?)?;
if is_archive_root_path(&path) || is_internal_metadata_path(&path) {
continue;
@@ -271,7 +310,10 @@ fn collect_archive_members(archive_path: &Path) -> Result<Vec<ArchiveMemberRecor
EntryType::Directory => "directory",
EntryType::Symlink => "symlink",
EntryType::Link => "hardlink",
- other => bail!("unsupported archive member type {other:?} in {}", archive_path.display()),
+ other => bail!(
+ "unsupported archive member type {other:?} in {}",
+ archive_path.display()
+ ),
};
members.push(ArchiveMemberRecord::new(path, kind));
}
@@ -279,7 +321,8 @@ fn collect_archive_members(archive_path: &Path) -> Result<Vec<ArchiveMemberRecor
}
fn sha256_file(path: &Path) -> Result<String> {
- let mut file = fs::File::open(path).with_context(|| format!("open rootfs file {}", path.display()))?;
+ let mut file =
+ fs::File::open(path).with_context(|| format!("open rootfs file {}", path.display()))?;
sha256_reader(&mut file, path)
}
@@ -287,7 +330,9 @@ fn sha256_reader(reader: &mut impl Read, source: &Path) -> Result<String> {
let mut hasher = Sha256::new();
let mut buffer = [0; 8192];
loop {
- let read = reader.read(&mut buffer).with_context(|| format!("read {}", source.display()))?;
+ let read = reader
+ .read(&mut buffer)
+ .with_context(|| format!("read {}", source.display()))?;
if read == 0 {
break;
}
@@ -298,9 +343,17 @@ fn sha256_reader(reader: &mut impl Read, source: &Path) -> Result<String> {
fn portable_path(path: &Path) -> Result<String> {
if path.is_absolute() {
- bail!("absolute path is not valid in an artifact manifest: {}", path.display());
+ bail!(
+ "absolute path is not valid in an artifact manifest: {}",
+ path.display()
+ );
}
- let value = path.to_str().ok_or_else(|| anyhow::anyhow!("non-UTF-8 path is not valid in an artifact manifest: {}", path.display()))?;
+ let value = path.to_str().ok_or_else(|| {
+ anyhow::anyhow!(
+ "non-UTF-8 path is not valid in an artifact manifest: {}",
+ path.display()
+ )
+ })?;
if value.is_empty() {
bail!("empty path is not valid in an artifact manifest");
}
@@ -310,19 +363,28 @@ fn portable_path(path: &Path) -> Result<String> {
std::path::Component::Normal(part) => normalized.push(part),
std::path::Component::CurDir => {}
std::path::Component::ParentDir => {
- bail!("parent path is not valid in an artifact manifest: {}", path.display());
+ bail!(
+ "parent path is not valid in an artifact manifest: {}",
+ path.display()
+ );
}
std::path::Component::RootDir | std::path::Component::Prefix(_) => {
- bail!("absolute path is not valid in an artifact manifest: {}", path.display());
+ bail!(
+ "absolute path is not valid in an artifact manifest: {}",
+ path.display()
+ );
}
}
}
if normalized.as_os_str().is_empty() {
return Ok(".".into());
}
- let normalized = normalized
- .to_str()
- .ok_or_else(|| anyhow::anyhow!("non-UTF-8 path is not valid in an artifact manifest: {}", path.display()))?;
+ let normalized = normalized.to_str().ok_or_else(|| {
+ anyhow::anyhow!(
+ "non-UTF-8 path is not valid in an artifact manifest: {}",
+ path.display()
+ )
+ })?;
Ok(normalized.to_owned())
}
@@ -349,7 +411,8 @@ fn is_enabled_service(path: &str) -> bool {
}
fn is_internal_metadata_path(path: &str) -> bool {
- path.split('/').any(|component| matches!(component, ".host" | ".fakedata"))
+ path.split('/')
+ .any(|component| matches!(component, ".host" | ".fakedata"))
}
fn is_archive_root_path(path: &str) -> bool {
@@ -364,7 +427,12 @@ fn service_name(path: &str) -> Result<&str> {
}
fn link_target(path: &Path) -> Result<String> {
- let value = path.to_str().ok_or_else(|| anyhow::anyhow!("non-UTF-8 symlink target is not valid in an artifact manifest: {}", path.display()))?;
+ let value = path.to_str().ok_or_else(|| {
+ anyhow::anyhow!(
+ "non-UTF-8 symlink target is not valid in an artifact manifest: {}",
+ path.display()
+ )
+ })?;
if value.is_empty() {
bail!("empty symlink target is not valid in an artifact manifest");
}
@@ -376,7 +444,11 @@ fn validate_manifest_paths(
initrd: Option<&InitrdRecord>,
archive_members: &[ArchiveMemberRecord],
) -> Result<()> {
- for path in files.iter().map(|record| record.path.as_str()).chain(archive_members.iter().map(|record| record.path.as_str())) {
+ for path in files
+ .iter()
+ .map(|record| record.path.as_str())
+ .chain(archive_members.iter().map(|record| record.path.as_str()))
+ {
portable_path(Path::new(path))?;
}
if let Some(initrd) = initrd {
@@ -386,10 +458,11 @@ fn validate_manifest_paths(
}
fn validate_initrd_record(record: Option<&InitrdRecord>) -> Result<()> {
- if let Some(record) = record {
- if record.sha256.is_empty() {
+ match record {
+ Some(record) if record.sha256.is_empty() => {
bail!("initrd record requires a sha256 digest: {}", record.path);
}
+ _ => {}
}
Ok(())
}
@@ -407,13 +480,21 @@ fn validate_file_records(records: &[FileRecord]) -> Result<()> {
}
"symlink" | "hardlink" => {
if record.digest.is_some() {
- bail!("{} record cannot carry a digest: {}", record.kind, record.path);
+ bail!(
+ "{} record cannot carry a digest: {}",
+ record.kind,
+ record.path
+ );
}
if record.target.as_deref().is_none_or(str::is_empty) {
bail!("{} record requires a target: {}", record.kind, record.path);
}
}
- _ => bail!("unsupported file record kind {}: {}", record.kind, record.path),
+ _ => bail!(
+ "unsupported file record kind {}: {}",
+ record.kind,
+ record.path
+ ),
}
}
Ok(())
@@ -437,7 +518,10 @@ pub struct PackageRecord {
impl PackageRecord {
pub fn new(name: impl Into<String>, version: impl Into<String>) -> Self {
- Self { name: name.into(), version: version.into() }
+ Self {
+ name: name.into(),
+ version: version.into(),
+ }
}
}
@@ -451,15 +535,30 @@ pub struct FileRecord {
impl FileRecord {
pub fn file(path: impl Into<String>, digest: impl Into<String>) -> Self {
- Self { path: path.into(), kind: "file".into(), digest: Some(digest.into()), target: None }
+ Self {
+ path: path.into(),
+ kind: "file".into(),
+ digest: Some(digest.into()),
+ target: None,
+ }
}
pub fn symlink(path: impl Into<String>, target: impl Into<String>) -> Self {
- Self { path: path.into(), kind: "symlink".into(), digest: None, target: Some(target.into()) }
+ Self {
+ path: path.into(),
+ kind: "symlink".into(),
+ digest: None,
+ target: Some(target.into()),
+ }
}
pub fn hardlink(path: impl Into<String>, target: impl Into<String>) -> Self {
- Self { path: path.into(), kind: "hardlink".into(), digest: None, target: Some(target.into()) }
+ Self {
+ path: path.into(),
+ kind: "hardlink".into(),
+ digest: None,
+ target: Some(target.into()),
+ }
}
}
@@ -471,7 +570,10 @@ pub struct InitrdRecord {
impl InitrdRecord {
pub fn new(path: impl Into<String>, sha256: impl Into<String>) -> Self {
- Self { path: path.into(), sha256: sha256.into() }
+ Self {
+ path: path.into(),
+ sha256: sha256.into(),
+ }
}
}
@@ -483,7 +585,10 @@ pub struct ServiceRecord {
impl ServiceRecord {
pub fn new(name: impl Into<String>, enabled: bool) -> Self {
- Self { name: name.into(), enabled }
+ Self {
+ name: name.into(),
+ enabled,
+ }
}
}
@@ -495,6 +600,9 @@ pub struct ArchiveMemberRecord {
impl ArchiveMemberRecord {
pub fn new(path: impl Into<String>, kind: impl Into<String>) -> Self {
- Self { path: path.into(), kind: kind.into() }
+ Self {
+ path: path.into(),
+ kind: kind.into(),
+ }
}
}
diff --git a/src/model.rs b/src/model.rs
index 13fa5e8..a0bc51c 100644
--- a/src/model.rs
+++ b/src/model.rs
@@ -2,7 +2,7 @@ use std::collections::HashSet;
use std::fs;
use std::path::Path;
-use anyhow::{bail, Context, Result};
+use anyhow::{Context, Result, bail};
use serde::Deserialize;
#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
@@ -130,9 +130,9 @@ impl ImageSpec {
for module in &self.boot.initrd_modules {
if module.is_empty()
|| module.contains('/')
- || !module
- .chars()
- .all(|character| character.is_ascii_alphanumeric() || matches!(character, '.' | '_' | '-'))
+ || !module.chars().all(|character| {
+ character.is_ascii_alphanumeric() || matches!(character, '.' | '_' | '-')
+ })
{
bail!("invalid initrd module: {module}");
}
diff --git a/src/package_installer.rs b/src/package_installer.rs
index 03e429f..ec84add 100644
--- a/src/package_installer.rs
+++ b/src/package_installer.rs
@@ -1,6 +1,6 @@
use std::path::{Path, PathBuf};
-use anyhow::{bail, Result};
+use anyhow::{Result, bail};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AptConfig(PathBuf);
@@ -12,7 +12,10 @@ impl AptConfig {
bail!("APT configuration must be a file path: {}", path.display());
}
if !path.is_file() {
- bail!("APT configuration does not exist or is not a regular file: {}", path.display());
+ bail!(
+ "APT configuration does not exist or is not a regular file: {}",
+ path.display()
+ );
}
Ok(Self(path.to_path_buf()))
}
diff --git a/src/rootfs.rs b/src/rootfs.rs
index ea7d87b..79406a7 100644
--- a/src/rootfs.rs
+++ b/src/rootfs.rs
@@ -3,9 +3,9 @@ use std::fs;
use std::os::unix::fs::symlink;
use std::path::{Path, PathBuf};
-use anyhow::{bail, Context, Result};
+use anyhow::{Context, Result, bail};
-use crate::files::{relative_symlink_is_safe, RootfsPath};
+use crate::files::{RootfsPath, relative_symlink_is_safe};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CopyTree {
@@ -127,7 +127,13 @@ fn copy_tree(rootfs: &Path, copy: &CopyTree, manifest: &mut MutationManifest) ->
if !copy.source.is_dir() {
bail!("copy source is not a directory: {}", copy.source.display());
}
- copy_entry(rootfs, &copy.source, copy.destination.as_path(), Path::new(""), manifest)
+ copy_entry(
+ rootfs,
+ &copy.source,
+ copy.destination.as_path(),
+ Path::new(""),
+ manifest,
+ )
}
fn copy_entry(
@@ -196,7 +202,11 @@ fn write_file(
Ok(())
}
-fn enable_service(rootfs: &Path, service: &ServiceName, manifest: &mut MutationManifest) -> Result<()> {
+fn enable_service(
+ rootfs: &Path,
+ service: &ServiceName,
+ manifest: &mut MutationManifest,
+) -> Result<()> {
let destination = RootfsPath::new(format!(
"etc/systemd/system/multi-user.target.wants/{}",
service.0
@@ -217,20 +227,28 @@ fn replace_with_file(source: &Path, output: &Path) -> Result<()> {
Ok(metadata) if metadata.file_type().is_dir() => {
bail!("cannot replace directory with file: {}", output.display());
}
- Ok(_) => fs::remove_file(output).with_context(|| format!("replace {}", output.display()))?,
+ Ok(_) => {
+ fs::remove_file(output).with_context(|| format!("replace {}", output.display()))?
+ }
Err(error) if error.kind() == std::io::ErrorKind::NotFound => {}
Err(error) => return Err(error).with_context(|| format!("inspect {}", output.display())),
}
- fs::copy(source, output).with_context(|| format!("copy {} to {}", source.display(), output.display()))?;
+ fs::copy(source, output)
+ .with_context(|| format!("copy {} to {}", source.display(), output.display()))?;
Ok(())
}
fn replace_with_symlink(target: &Path, output: &Path) -> Result<()> {
match fs::symlink_metadata(output) {
Ok(metadata) if metadata.file_type().is_dir() => {
- bail!("cannot replace directory with symlink: {}", output.display());
+ bail!(
+ "cannot replace directory with symlink: {}",
+ output.display()
+ );
+ }
+ Ok(_) => {
+ fs::remove_file(output).with_context(|| format!("replace {}", output.display()))?
}
- Ok(_) => fs::remove_file(output).with_context(|| format!("replace {}", output.display()))?,
Err(error) if error.kind() == std::io::ErrorKind::NotFound => {}
Err(error) => return Err(error).with_context(|| format!("inspect {}", output.display())),
}
diff --git a/tests/archive.rs b/tests/archive.rs
index 6e59837..83f6722 100644
--- a/tests/archive.rs
+++ b/tests/archive.rs
@@ -20,10 +20,17 @@ fn writes_deterministic_tar_with_files_directories_and_symlinks() {
let first = fixture.path().join("first.tar");
let second = fixture.path().join("second.tar");
- NativeTarWriter::new().write(&rootfs, &first).expect("write first archive");
- NativeTarWriter::new().write(&rootfs, &second).expect("write second archive");
+ NativeTarWriter::new()
+ .write(&rootfs, &first)
+ .expect("write first archive");
+ NativeTarWriter::new()
+ .write(&rootfs, &second)
+ .expect("write second archive");
- assert_eq!(fs::read(&first).expect("read first"), fs::read(&second).expect("read second"));
+ assert_eq!(
+ fs::read(&first).expect("read first"),
+ fs::read(&second).expect("read second")
+ );
let mut archive = Archive::new(fs::File::open(first).expect("open archive"));
let entries = archive
@@ -32,7 +39,13 @@ fn writes_deterministic_tar_with_files_directories_and_symlinks() {
.map(|entry| {
let entry = entry.expect("read entry");
let path = entry.path().expect("entry path").into_owned();
- (path, entry.header().entry_type(), entry.header().mtime().expect("mtime"), entry.header().uid().expect("uid"), entry.header().gid().expect("gid"))
+ (
+ path,
+ entry.header().entry_type(),
+ entry.header().mtime().expect("mtime"),
+ entry.header().uid().expect("uid"),
+ entry.header().gid().expect("gid"),
+ )
})
.collect::<Vec<_>>();
@@ -78,7 +91,10 @@ fn preserves_hardlinks_in_the_archive() {
(
entry.path().expect("entry path").into_owned(),
entry.header().entry_type(),
- entry.link_name().expect("entry link name").map(|path| path.into_owned()),
+ entry
+ .link_name()
+ .expect("entry link name")
+ .map(|path| path.into_owned()),
)
})
.collect::<Vec<_>>();
@@ -102,7 +118,10 @@ fn preserves_hardlinks_in_the_archive() {
fn rejects_a_missing_rootfs() {
let fixture = tempdir().expect("fixture directory");
let error = NativeTarWriter::new()
- .write(fixture.path().join("missing"), fixture.path().join("image.tar"))
+ .write(
+ fixture.path().join("missing"),
+ fixture.path().join("image.tar"),
+ )
.expect_err("missing rootfs must fail");
assert!(error.to_string().contains("rootfs is not a directory"));
}
@@ -119,7 +138,11 @@ fn rejects_an_archive_output_inside_the_rootfs() {
.write(&rootfs, &artifact)
.expect_err("archive output inside rootfs must be rejected");
- assert!(error.to_string().contains("archive output must not be inside rootfs"));
+ assert!(
+ error
+ .to_string()
+ .contains("archive output must not be inside rootfs")
+ );
assert!(!artifact.exists());
assert!(!rootfs.join("controller.tar.partial").exists());
}
@@ -138,7 +161,10 @@ fn refuses_to_replace_an_existing_archive() {
.expect_err("an existing archive must not be replaced");
assert!(error.to_string().contains("archive output already exists"));
- assert_eq!(fs::read_to_string(&artifact).expect("read existing artifact"), "existing artifact");
+ assert_eq!(
+ fs::read_to_string(&artifact).expect("read existing artifact"),
+ "existing artifact"
+ );
assert!(!fixture.path().join("controller.tar.partial").exists());
}
@@ -155,5 +181,8 @@ fn removes_partial_output_when_an_unsupported_entry_stops_packaging() {
.expect_err("socket entries must be rejected");
assert!(error.to_string().contains("unsupported rootfs entry type"));
- assert!(!artifact.exists(), "failed packaging must not publish a partial archive");
+ assert!(
+ !artifact.exists(),
+ "failed packaging must not publish a partial archive"
+ );
}
diff --git a/tests/build.rs b/tests/build.rs
index a0a75b8..7d67b29 100644
--- a/tests/build.rs
+++ b/tests/build.rs
@@ -119,7 +119,12 @@ fn refuses_to_reuse_an_existing_workspace() {
fs::create_dir(&workspace).expect("create existing workspace");
let error = executor
- .execute(&plan, &workspace, "profiles/apt.conf", fixture.path().join("image.tar"))
+ .execute(
+ &plan,
+ &workspace,
+ "profiles/apt.conf",
+ fixture.path().join("image.tar"),
+ )
.expect_err("existing workspace must be rejected");
assert!(error.to_string().contains("workspace already exists"));
@@ -143,7 +148,10 @@ fn rejects_an_existing_artifact_before_creating_the_workspace_or_installing() {
.expect_err("pre-existing artifact must be rejected");
assert!(error.to_string().contains("output artifact already exists"));
- assert_eq!(fs::read_to_string(&artifact).expect("read artifact"), "do not replace");
+ assert_eq!(
+ fs::read_to_string(&artifact).expect("read artifact"),
+ "do not replace"
+ );
assert!(!workspace.exists());
assert!(installer.requests.borrow().is_empty());
}
@@ -166,7 +174,10 @@ fn rejects_an_existing_companion_manifest_before_creating_the_workspace_or_insta
.expect_err("pre-existing companion manifest must be rejected");
assert!(error.to_string().contains("output manifest already exists"));
- assert_eq!(fs::read_to_string(&companion).expect("read companion manifest"), "do not replace");
+ assert_eq!(
+ fs::read_to_string(&companion).expect("read companion manifest"),
+ "do not replace"
+ );
assert!(!workspace.exists());
assert!(installer.requests.borrow().is_empty());
}
@@ -183,10 +194,19 @@ fn rejects_a_missing_apt_configuration_before_creating_the_workspace() {
let missing_apt_config = fixture.path().join("missing-apt.conf");
let error = executor
- .execute(&plan, &workspace, &missing_apt_config, fixture.path().join("image.tar"))
+ .execute(
+ &plan,
+ &workspace,
+ &missing_apt_config,
+ fixture.path().join("image.tar"),
+ )
.expect_err("missing APT configuration must be rejected");
- assert!(error.to_string().contains("APT configuration does not exist"));
+ assert!(
+ error
+ .to_string()
+ .contains("APT configuration does not exist")
+ );
assert!(!workspace.exists());
assert!(installer.requests.borrow().is_empty());
}
@@ -205,8 +225,15 @@ fn removes_the_new_workspace_when_package_installation_fails() {
.execute(&plan, &workspace, "profiles/apt.conf", &artifact)
.expect_err("failed installation must fail the build");
- assert!(error.to_string().contains("simulated package installation failure"));
- assert!(!workspace.exists(), "failed build must not leave a workspace");
+ assert!(
+ error
+ .to_string()
+ .contains("simulated package installation failure")
+ );
+ assert!(
+ !workspace.exists(),
+ "failed build must not leave a workspace"
+ );
assert!(!artifact.exists());
}
@@ -224,8 +251,15 @@ fn removes_the_new_workspace_when_the_installer_does_not_create_a_rootfs() {
.execute(&plan, &workspace, "profiles/apt.conf", &artifact)
.expect_err("missing rootfs must fail the build");
- assert!(error.to_string().contains("package installer did not create rootfs"));
- assert!(!workspace.exists(), "failed build must not leave a workspace");
+ assert!(
+ error
+ .to_string()
+ .contains("package installer did not create rootfs")
+ );
+ assert!(
+ !workspace.exists(),
+ "failed build must not leave a workspace"
+ );
assert!(!artifact.exists());
}
@@ -235,7 +269,8 @@ fn removes_the_new_workspace_when_rootfs_finalization_fails() {
let spec = ImageSpec::load(std::path::Path::new("profiles/alt-controller.toml"))
.expect("load controller spec");
let plan = BuildPlan::compile(spec).expect("compile build plan");
- let mut executor = BuildExecutor::new(RootfsFinalizationFailingInstaller, FixtureInitramfsBuilder);
+ let mut executor =
+ BuildExecutor::new(RootfsFinalizationFailingInstaller, FixtureInitramfsBuilder);
let workspace = fixture.path().join("work");
let artifact = fixture.path().join("image.tar");
@@ -243,7 +278,10 @@ fn removes_the_new_workspace_when_rootfs_finalization_fails() {
.execute(&plan, &workspace, "profiles/apt.conf", &artifact)
.expect_err("failed rootfs finalization must fail the build");
- assert!(!workspace.exists(), "failed build must not leave a workspace");
+ assert!(
+ !workspace.exists(),
+ "failed build must not leave a workspace"
+ );
assert!(!artifact.exists());
}
@@ -261,7 +299,14 @@ fn removes_the_new_workspace_when_initramfs_build_fails() {
.execute(&plan, &workspace, "profiles/apt.conf", &artifact)
.expect_err("failed initramfs build must fail the build");
- assert!(error.to_string().contains("simulated initramfs build failure"));
- assert!(!workspace.exists(), "failed build must not leave a workspace");
+ assert!(
+ error
+ .to_string()
+ .contains("simulated initramfs build failure")
+ );
+ assert!(
+ !workspace.exists(),
+ "failed build must not leave a workspace"
+ );
assert!(!artifact.exists());
}
diff --git a/tests/cli.rs b/tests/cli.rs
index 648962b..dfaf140 100644
--- a/tests/cli.rs
+++ b/tests/cli.rs
@@ -2,7 +2,7 @@ use std::fs;
use std::path::PathBuf;
use alt_controller_image::archive::NativeTarWriter;
-use alt_controller_image::cli::{run, Cli, Command};
+use alt_controller_image::cli::{Cli, Command, run};
use alt_controller_image::manifest::{ArtifactManifest, PackageRecord};
use clap::Parser;
use tempfile::tempdir;
@@ -46,8 +46,13 @@ fn parses_build_with_explicit_workspace_and_output_paths() {
#[test]
fn parses_inspect_with_an_explicit_artifact_path() {
- let cli = Cli::try_parse_from(["alt-controller-image", "inspect", "--artifact", "out/image.tar"])
- .expect("inspect command parses");
+ let cli = Cli::try_parse_from([
+ "alt-controller-image",
+ "inspect",
+ "--artifact",
+ "out/image.tar",
+ ])
+ .expect("inspect command parses");
assert!(matches!(
cli.command,
@@ -81,23 +86,37 @@ fn compare_uses_companion_manifests_for_tar_artifacts() {
fs::create_dir_all(&rootfs).expect("create rootfs");
let left = fixture.path().join("left.tar");
let right = fixture.path().join("right.tar");
- NativeTarWriter::new().write(&rootfs, &left).expect("write left archive");
- NativeTarWriter::new().write(&rootfs, &right).expect("write right archive");
+ NativeTarWriter::new()
+ .write(&rootfs, &left)
+ .expect("write left archive");
+ NativeTarWriter::new()
+ .write(&rootfs, &right)
+ .expect("write right archive");
ArtifactManifest::new(
- vec![PackageRecord::new("controller", "1.0")], vec![], None, vec![], vec![],
+ vec![PackageRecord::new("controller", "1.0")],
+ vec![],
+ None,
+ vec![],
+ vec![],
)
.expect("left manifest")
.write_beside(&left)
.expect("write left companion manifest");
ArtifactManifest::new(
- vec![PackageRecord::new("controller", "2.0")], vec![], None, vec![], vec![],
+ vec![PackageRecord::new("controller", "2.0")],
+ vec![],
+ None,
+ vec![],
+ vec![],
)
.expect("right manifest")
.write_beside(&right)
.expect("write right companion manifest");
- let error = run(Cli { command: Command::Compare { left, right } })
- .expect_err("companion package difference must be reported");
+ let error = run(Cli {
+ command: Command::Compare { left, right },
+ })
+ .expect_err("companion package difference must be reported");
assert!(error.to_string().contains("artifacts differ semantically"));
}
diff --git a/tests/compare.rs b/tests/compare.rs
index ab63f9a..8ea9fdb 100644
--- a/tests/compare.rs
+++ b/tests/compare.rs
@@ -1,5 +1,5 @@
-use alt_controller_image::compare::{Change, SemanticDifference, compare};
use alt_controller_image::archive::NativeTarWriter;
+use alt_controller_image::compare::{Change, SemanticDifference, compare};
use alt_controller_image::manifest::{
ArchiveMemberRecord, ArtifactManifest, FileRecord, InitrdRecord, PackageRecord, ServiceRecord,
};
@@ -27,7 +27,10 @@ fn compares_added_removed_and_changed_semantic_records() {
PackageRecord::new("kernel-image-rt", "2.0"),
PackageRecord::new("controller-agent", "1.0"),
],
- vec![FileRecord::symlink("etc/controller.conf", "controller.conf.real")],
+ vec![FileRecord::symlink(
+ "etc/controller.conf",
+ "controller.conf.real",
+ )],
Some(InitrdRecord::new("boot/initrd-rt.img", "new-initrd")),
vec![ServiceRecord::new("controller.service", false)],
vec![ArchiveMemberRecord::new("usr/bin/controller", "file")],
@@ -39,13 +42,33 @@ fn compares_added_removed_and_changed_semantic_records() {
assert_eq!(
report.differences(),
&[
- SemanticDifference::Package { name: "controller-agent".into(), change: Change::Added },
- SemanticDifference::Package { name: "kernel-image-rt".into(), change: Change::Changed },
- SemanticDifference::File { path: "etc/controller.conf".into(), change: Change::Changed },
- SemanticDifference::Initrd { change: Change::Changed },
- SemanticDifference::Service { name: "controller.service".into(), change: Change::Changed },
- SemanticDifference::ArchiveMember { path: "etc/controller.conf".into(), change: Change::Removed },
- SemanticDifference::ArchiveMember { path: "usr/bin/controller".into(), change: Change::Added },
+ SemanticDifference::Package {
+ name: "controller-agent".into(),
+ change: Change::Added
+ },
+ SemanticDifference::Package {
+ name: "kernel-image-rt".into(),
+ change: Change::Changed
+ },
+ SemanticDifference::File {
+ path: "etc/controller.conf".into(),
+ change: Change::Changed
+ },
+ SemanticDifference::Initrd {
+ change: Change::Changed
+ },
+ SemanticDifference::Service {
+ name: "controller.service".into(),
+ change: Change::Changed
+ },
+ SemanticDifference::ArchiveMember {
+ path: "etc/controller.conf".into(),
+ change: Change::Removed
+ },
+ SemanticDifference::ArchiveMember {
+ path: "usr/bin/controller".into(),
+ change: Change::Added
+ },
]
);
assert!(report.render().contains("changed initrd"));
@@ -98,10 +121,12 @@ fn archive_collection_records_an_initrd_once_as_a_dedicated_boot_fact() {
"8f7ed204b9dfaa20aa484445f54233c4b407cb80ec0f8c07f1f0a59675fb44cf",
))
);
- assert!(manifest
- .files
- .iter()
- .all(|record| record.path != "boot/initrd-6.12-rt1.img"));
+ assert!(
+ manifest
+ .files
+ .iter()
+ .all(|record| record.path != "boot/initrd-6.12-rt1.img")
+ );
}
#[test]
@@ -123,10 +148,12 @@ fn rootfs_collection_records_a_supplied_initrd_once_as_a_dedicated_boot_fact() {
.expect("collect rootfs facts");
assert_eq!(manifest.initrd, Some(initrd));
- assert!(manifest
- .files
- .iter()
- .all(|record| record.path != "boot/initrd-6.12-rt1.img"));
+ assert!(
+ manifest
+ .files
+ .iter()
+ .all(|record| record.path != "boot/initrd-6.12-rt1.img")
+ );
}
#[test]
@@ -136,8 +163,14 @@ fn reads_and_writes_a_toml_manifest_beside_an_artifact() {
let manifest = baseline();
let manifest_path = manifest.write_beside(&artifact).expect("write manifest");
- assert_eq!(manifest_path, fixture.path().join("controller.tar.manifest.toml"));
- assert_eq!(ArtifactManifest::load(&manifest_path).expect("load manifest"), manifest);
+ assert_eq!(
+ manifest_path,
+ fixture.path().join("controller.tar.manifest.toml")
+ );
+ assert_eq!(
+ ArtifactManifest::load(&manifest_path).expect("load manifest"),
+ manifest
+ );
}
#[test]
@@ -146,16 +179,26 @@ fn writes_distinct_manifests_for_artifacts_in_the_same_directory() {
let legacy = fixture.path().join("legacy.tar");
let native = fixture.path().join("native.tar");
let legacy_manifest = baseline();
- let native_manifest = ArtifactManifest::new(vec![], vec![], None, vec![], vec![])
- .expect("valid native manifest");
+ let native_manifest =
+ ArtifactManifest::new(vec![], vec![], None, vec![], vec![]).expect("valid native manifest");
- let legacy_path = legacy_manifest.write_beside(&legacy).expect("write legacy manifest");
- let native_path = native_manifest.write_beside(&native).expect("write native manifest");
+ let legacy_path = legacy_manifest
+ .write_beside(&legacy)
+ .expect("write legacy manifest");
+ let native_path = native_manifest
+ .write_beside(&native)
+ .expect("write native manifest");
assert_eq!(legacy_path, fixture.path().join("legacy.tar.manifest.toml"));
assert_eq!(native_path, fixture.path().join("native.tar.manifest.toml"));
- assert_eq!(ArtifactManifest::load(&legacy_path).expect("load legacy manifest"), legacy_manifest);
- assert_eq!(ArtifactManifest::load(&native_path).expect("load native manifest"), native_manifest);
+ assert_eq!(
+ ArtifactManifest::load(&legacy_path).expect("load legacy manifest"),
+ legacy_manifest
+ );
+ assert_eq!(
+ ArtifactManifest::load(&native_path).expect("load native manifest"),
+ native_manifest
+ );
}
#[test]
@@ -170,13 +213,19 @@ fn refuses_to_overwrite_an_existing_companion_manifest() {
.expect_err("existing companion manifests must not be overwritten");
assert!(error.to_string().contains("create artifact manifest"));
- assert_eq!(fs::read_to_string(&manifest_path).expect("read existing manifest"), "preserve this manifest");
+ assert_eq!(
+ fs::read_to_string(&manifest_path).expect("read existing manifest"),
+ "preserve this manifest"
+ );
}
#[test]
fn rejects_duplicate_semantic_keys() {
let error = ArtifactManifest::new(
- vec![PackageRecord::new("controller", "1"), PackageRecord::new("controller", "2")],
+ vec![
+ PackageRecord::new("controller", "1"),
+ PackageRecord::new("controller", "2"),
+ ],
vec![],
None,
vec![],
@@ -202,7 +251,11 @@ fn rejects_malformed_file_records_before_comparison() {
vec![],
)
.expect_err("regular files require a digest");
- assert!(error.to_string().contains("regular file record requires a digest"));
+ assert!(
+ error
+ .to_string()
+ .contains("regular file record requires a digest")
+ );
let error = ArtifactManifest::new(
vec![],
@@ -217,7 +270,11 @@ fn rejects_malformed_file_records_before_comparison() {
vec![],
)
.expect_err("symlinks cannot carry a digest");
- assert!(error.to_string().contains("symlink record cannot carry a digest"));
+ assert!(
+ error
+ .to_string()
+ .contains("symlink record cannot carry a digest")
+ );
}
#[test]
@@ -231,7 +288,11 @@ fn rejects_malformed_initrd_records_before_comparison() {
)
.expect_err("initrd records require a digest");
- assert!(error.to_string().contains("initrd record requires a sha256 digest"));
+ assert!(
+ error
+ .to_string()
+ .contains("initrd record requires a sha256 digest")
+ );
}
#[test]
@@ -283,24 +344,37 @@ fn collects_semantic_facts_from_native_rootfs_and_tar_artifact() {
)
.expect("collect semantic facts");
- assert_eq!(manifest.packages, vec![PackageRecord::new("controller", "1.0")]);
- assert!(manifest.files.iter().any(|record| record == &FileRecord::file(
- "etc/controller.conf",
- "2d5c759b2b539229e09d362e8dbe0ae410ff8c9ece6038624458724520683f5b",
- )));
- assert!(manifest
- .files
- .iter()
- .any(|record| record == &FileRecord::symlink("etc/controller-link", "controller.conf")));
- assert_eq!(manifest.services, vec![ServiceRecord::new("controller.service", true)]);
- assert!(manifest
- .archive_members
- .iter()
- .any(|record| record == &ArchiveMemberRecord::new("etc/controller.conf", "file")));
- assert!(manifest
- .archive_members
- .iter()
- .any(|record| record == &ArchiveMemberRecord::new("etc/controller-link", "symlink")));
+ assert_eq!(
+ manifest.packages,
+ vec![PackageRecord::new("controller", "1.0")]
+ );
+ assert!(manifest.files.iter().any(|record| record
+ == &FileRecord::file(
+ "etc/controller.conf",
+ "2d5c759b2b539229e09d362e8dbe0ae410ff8c9ece6038624458724520683f5b",
+ )));
+ assert!(
+ manifest
+ .files
+ .iter()
+ .any(|record| record == &FileRecord::symlink("etc/controller-link", "controller.conf"))
+ );
+ assert_eq!(
+ manifest.services,
+ vec![ServiceRecord::new("controller.service", true)]
+ );
+ assert!(
+ manifest
+ .archive_members
+ .iter()
+ .any(|record| record == &ArchiveMemberRecord::new("etc/controller.conf", "file"))
+ );
+ assert!(
+ manifest
+ .archive_members
+ .iter()
+ .any(|record| record == &ArchiveMemberRecord::new("etc/controller-link", "symlink"))
+ );
}
#[test]
@@ -325,10 +399,11 @@ fn collects_comparable_semantic_facts_directly_from_a_tar_artifact() {
let manifest = ArtifactManifest::collect_archive(&artifact).expect("collect archive facts");
assert_eq!(manifest.packages, Vec::<PackageRecord>::new());
- assert!(manifest.files.iter().any(|record| record == &FileRecord::file(
- "etc/controller.conf",
- "2d5c759b2b539229e09d362e8dbe0ae410ff8c9ece6038624458724520683f5b",
- )));
+ assert!(manifest.files.iter().any(|record| record
+ == &FileRecord::file(
+ "etc/controller.conf",
+ "2d5c759b2b539229e09d362e8dbe0ae410ff8c9ece6038624458724520683f5b",
+ )));
assert_eq!(
manifest.initrd,
Some(InitrdRecord::new(
@@ -336,7 +411,10 @@ fn collects_comparable_semantic_facts_directly_from_a_tar_artifact() {
"8f7ed204b9dfaa20aa484445f54233c4b407cb80ec0f8c07f1f0a59675fb44cf",
))
);
- assert_eq!(manifest.services, vec![ServiceRecord::new("controller.service", true)]);
+ assert_eq!(
+ manifest.services,
+ vec![ServiceRecord::new("controller.service", true)]
+ );
}
#[test]
@@ -350,11 +428,16 @@ fn normalizes_legacy_dot_prefixed_archive_paths_before_comparison() {
header.set_mode(0o644);
header.set_cksum();
archive
- .append_data(&mut header, "./etc/controller.conf", Cursor::new(b"controller\n"))
+ .append_data(
+ &mut header,
+ "./etc/controller.conf",
+ Cursor::new(b"controller\n"),
+ )
.expect("append legacy-style member");
archive.finish().expect("finish legacy archive");
- let manifest = ArtifactManifest::collect_archive(&artifact).expect("collect legacy archive facts");
+ let manifest =
+ ArtifactManifest::collect_archive(&artifact).expect("collect legacy archive facts");
let expected = ArtifactManifest::new(
vec![],
vec![FileRecord::file(
@@ -391,7 +474,11 @@ fn rejects_duplicate_archive_member_paths_before_semantic_comparison() {
let error = ArtifactManifest::collect_archive(&artifact)
.expect_err("ambiguous archive paths cannot be compared semantically");
- assert!(error.to_string().contains("duplicate archive member record: etc/controller.conf"));
+ assert!(
+ error
+ .to_string()
+ .contains("duplicate archive member record: etc/controller.conf")
+ );
}
#[test]
@@ -405,7 +492,11 @@ fn collects_hardlink_targets_from_an_archive_for_semantic_comparison() {
header.set_mode(0o644);
header.set_cksum();
archive
- .append_data(&mut header, "./usr/bin/controller", Cursor::new(b"controller\n"))
+ .append_data(
+ &mut header,
+ "./usr/bin/controller",
+ Cursor::new(b"controller\n"),
+ )
.expect("append regular member");
let mut link_header = tar::Header::new_gnu();
link_header.set_entry_type(tar::EntryType::Link);
@@ -447,9 +538,10 @@ fn rootfs_collection_preserves_hardlink_facts_like_archive_collection() {
.write(&rootfs, &artifact)
.expect("write native archive");
- let rootfs_manifest = ArtifactManifest::collect(&rootfs, vec![], None, &artifact)
- .expect("collect rootfs facts");
- let archive_manifest = ArtifactManifest::collect_archive(&artifact).expect("collect archive facts");
+ let rootfs_manifest =
+ ArtifactManifest::collect(&rootfs, vec![], None, &artifact).expect("collect rootfs facts");
+ let archive_manifest =
+ ArtifactManifest::collect_archive(&artifact).expect("collect archive facts");
assert_eq!(rootfs_manifest.files, archive_manifest.files);
assert!(rootfs_manifest.files.iter().any(|record| {
@@ -464,17 +556,28 @@ fn normalizes_dot_prefixed_paths_from_a_legacy_tree_archive() {
fs::create_dir_all(rootfs.join("etc")).expect("create rootfs directory");
fs::write(rootfs.join("etc/controller.conf"), "controller\n").expect("write rootfs file");
let artifact = fixture.path().join("legacy.tar");
- let mut archive = tar::Builder::new(fs::File::create(&artifact).expect("create legacy archive"));
- archive.append_dir_all(".", &rootfs).expect("write legacy tree archive");
+ let mut archive =
+ tar::Builder::new(fs::File::create(&artifact).expect("create legacy archive"));
+ archive
+ .append_dir_all(".", &rootfs)
+ .expect("write legacy tree archive");
archive.finish().expect("finish legacy archive");
- let manifest = ArtifactManifest::collect_archive(&artifact).expect("collect legacy archive facts");
+ let manifest =
+ ArtifactManifest::collect_archive(&artifact).expect("collect legacy archive facts");
- assert!(manifest.files.iter().all(|record| !record.path.starts_with("./")));
- assert!(manifest
- .archive_members
- .iter()
- .all(|record| !record.path.starts_with("./")));
+ assert!(
+ manifest
+ .files
+ .iter()
+ .all(|record| !record.path.starts_with("./"))
+ );
+ assert!(
+ manifest
+ .archive_members
+ .iter()
+ .all(|record| !record.path.starts_with("./"))
+ );
}
#[test]
@@ -489,20 +592,26 @@ fn excludes_legacy_internal_host_and_fakedata_archive_members() {
fs::write(rootfs.join("etc/controller.conf"), "controller\n").expect("write visible file");
let legacy = fixture.path().join("legacy.tar");
let native = fixture.path().join("native.tar");
- let mut legacy_writer = tar::Builder::new(fs::File::create(&legacy).expect("create legacy archive"));
+ let mut legacy_writer =
+ tar::Builder::new(fs::File::create(&legacy).expect("create legacy archive"));
legacy_writer
.append_dir_all(".", &rootfs)
.expect("write legacy archive with internal metadata");
legacy_writer.finish().expect("finish legacy archive");
- NativeTarWriter::new().write(&rootfs, &native).expect("write native archive");
+ NativeTarWriter::new()
+ .write(&rootfs, &native)
+ .expect("write native archive");
let legacy_manifest = ArtifactManifest::collect_archive(&legacy).expect("collect legacy facts");
let native_manifest = ArtifactManifest::collect_archive(&native).expect("collect native facts");
- assert!(legacy_manifest
- .archive_members
- .iter()
- .all(|record| !record.path.starts_with(".host/") && !record.path.starts_with(".fakedata/")));
+ assert!(
+ legacy_manifest
+ .archive_members
+ .iter()
+ .all(|record| !record.path.starts_with(".host/")
+ && !record.path.starts_with(".fakedata/"))
+ );
let report = compare(&legacy_manifest, &native_manifest);
assert!(report.is_equivalent(), "{}", report.render());
}
@@ -519,16 +628,21 @@ fn native_manifest_collection_excludes_internal_members_from_its_archive_facts()
fs::write(rootfs.join("etc/controller.conf"), "controller\n").expect("write visible file");
let artifact = fixture.path().join("legacy.tar");
let mut writer = tar::Builder::new(fs::File::create(&artifact).expect("create legacy archive"));
- writer.append_dir_all(".", &rootfs).expect("write legacy archive");
+ writer
+ .append_dir_all(".", &rootfs)
+ .expect("write legacy archive");
writer.finish().expect("finish legacy archive");
let manifest = ArtifactManifest::collect(&rootfs, vec![], None, &artifact)
.expect("collect native filesystem and archive facts");
- assert!(manifest
- .archive_members
- .iter()
- .all(|record| !record.path.starts_with(".host/") && !record.path.starts_with(".fakedata/")));
+ assert!(
+ manifest
+ .archive_members
+ .iter()
+ .all(|record| !record.path.starts_with(".host/")
+ && !record.path.starts_with(".fakedata/"))
+ );
}
#[test]
@@ -541,19 +655,25 @@ fn archive_collection_excludes_nested_internal_metadata_members() {
fs::write(rootfs.join("etc/controller.conf"), "controller\n").expect("write visible file");
let artifact = fixture.path().join("legacy.tar");
let mut writer = tar::Builder::new(fs::File::create(&artifact).expect("create legacy archive"));
- writer.append_dir_all(".", &rootfs).expect("write legacy archive");
+ writer
+ .append_dir_all(".", &rootfs)
+ .expect("write legacy archive");
writer.finish().expect("finish legacy archive");
let manifest = ArtifactManifest::collect_archive(&artifact).expect("collect archive facts");
- assert!(manifest
- .archive_members
- .iter()
- .all(|record| !record.path.split('/').any(|component| component == ".fakedata")));
- assert!(manifest
- .files
- .iter()
- .all(|record| !record.path.split('/').any(|component| component == ".fakedata")));
+ assert!(manifest.archive_members.iter().all(|record| {
+ !record
+ .path
+ .split('/')
+ .any(|component| component == ".fakedata")
+ }));
+ assert!(manifest.files.iter().all(|record| {
+ !record
+ .path
+ .split('/')
+ .any(|component| component == ".fakedata")
+ }));
}
#[test]
@@ -575,7 +695,10 @@ fn rootfs_collection_recognizes_services_enabled_by_non_default_targets() {
let manifest = ArtifactManifest::collect(&rootfs, vec![], None, &artifact)
.expect("collect native filesystem and archive facts");
- assert_eq!(manifest.services, vec![ServiceRecord::new("controller.service", true)]);
+ assert_eq!(
+ manifest.services,
+ vec![ServiceRecord::new("controller.service", true)]
+ );
}
#[test]
@@ -598,7 +721,10 @@ fn archive_collection_coalesces_a_service_enabled_by_multiple_targets() {
let manifest = ArtifactManifest::collect_archive(&artifact).expect("collect archive facts");
- assert_eq!(manifest.services, vec![ServiceRecord::new("controller.service", true)]);
+ assert_eq!(
+ manifest.services,
+ vec![ServiceRecord::new("controller.service", true)]
+ );
}
#[test]
diff --git a/tests/initramfs.rs b/tests/initramfs.rs
index 429411a..5ad0b99 100644
--- a/tests/initramfs.rs
+++ b/tests/initramfs.rs
@@ -12,10 +12,8 @@ use tempfile::tempdir;
fn discovers_the_single_rt_kernel_and_renders_a_sorted_oem_recipe() {
let rootfs = tempdir().expect("rootfs directory");
fs::create_dir(rootfs.path().join("boot")).expect("boot directory");
- fs::write(rootfs.path().join("boot/vmlinuz-6.12.8-alt1.rt1"), "kernel")
- .expect("RT kernel");
- fs::write(rootfs.path().join("boot/vmlinuz-6.12.8-alt1.std"), "kernel")
- .expect("non-RT kernel");
+ fs::write(rootfs.path().join("boot/vmlinuz-6.12.8-alt1.rt1"), "kernel").expect("RT kernel");
+ fs::write(rootfs.path().join("boot/vmlinuz-6.12.8-alt1.std"), "kernel").expect("non-RT kernel");
let kernel = KernelVersion::discover_rt(rootfs.path()).expect("discover RT kernel");
assert_eq!(kernel.as_str(), "6.12.8-alt1.rt1");
@@ -59,8 +57,11 @@ fn make_initrd_adapter_uses_typed_chroot_arguments_and_records_output_digest() {
let rootfs = tempdir().expect("rootfs directory");
fs::create_dir(rootfs.path().join("boot")).expect("boot directory");
fs::write(rootfs.path().join("boot/vmlinuz-6.12-rt1"), "kernel").expect("RT kernel");
- fs::write(rootfs.path().join("boot/initrd-6.12-rt1.img"), b"generated initrd")
- .expect("generated initrd");
+ fs::write(
+ rootfs.path().join("boot/initrd-6.12-rt1.img"),
+ b"generated initrd",
+ )
+ .expect("generated initrd");
let request = InitramfsRequest::discover(rootfs.path()).expect("initramfs request");
let mut builder = MakeInitrdBuilder::new(RecordingRunner::default());
@@ -104,10 +105,19 @@ fn initramfs_adapter_refuses_to_replace_a_non_symlink_boot_alias() {
let request = InitramfsRequest::discover(rootfs.path()).expect("initramfs request");
let mut builder = MakeInitrdBuilder::new(RecordingRunner::default());
- let error = builder.build(&request).expect_err("regular boot alias must be protected");
+ let error = builder
+ .build(&request)
+ .expect_err("regular boot alias must be protected");
- assert!(error.to_string().contains("refusing to replace non-symlink boot alias"));
- assert_eq!(fs::read_to_string(boot.join("initrd.img")).expect("protected alias"), "do not replace");
+ assert!(
+ error
+ .to_string()
+ .contains("refusing to replace non-symlink boot alias")
+ );
+ assert_eq!(
+ fs::read_to_string(boot.join("initrd.img")).expect("protected alias"),
+ "do not replace"
+ );
}
#[derive(Debug, Default)]
diff --git a/tests/model_validation.rs b/tests/model_validation.rs
index f94a656..92a6640 100644
--- a/tests/model_validation.rs
+++ b/tests/model_validation.rs
@@ -56,7 +56,10 @@ fn controller_spec_preserves_the_vendored_controller_package_set() {
"rsyslog",
] {
assert!(
- spec.packages.base.iter().any(|candidate| candidate == package),
+ spec.packages
+ .base
+ .iter()
+ .any(|candidate| candidate == package),
"controller package set must contain {package}"
);
}
diff --git a/tests/package_installer.rs b/tests/package_installer.rs
index e527f14..76d8683 100644
--- a/tests/package_installer.rs
+++ b/tests/package_installer.rs
@@ -1,8 +1,8 @@
use std::path::PathBuf;
use alt_controller_image::hasher::{CommandRunner, HasherInstaller, Invocation};
-use alt_controller_image::{BuildPlan, ImageSpec};
use alt_controller_image::package_installer::{AptConfig, PackageInstaller, PackageRequest};
+use alt_controller_image::{BuildPlan, ImageSpec};
#[derive(Default)]
struct RecordingRunner {
@@ -63,13 +63,15 @@ fn hasher_installer_passes_typed_request_to_initroot_and_install_commands() {
#[test]
fn package_request_rejects_empty_packages_and_unsafe_apt_config_paths() {
assert!(AptConfig::new("").is_err());
- assert!(PackageRequest::new(
- PathBuf::from("work"),
- AptConfig::new("profiles/apt.conf").expect("valid apt config"),
- ["basesystem", ""],
- std::iter::empty::<&str>(),
- )
- .is_err());
+ assert!(
+ PackageRequest::new(
+ PathBuf::from("work"),
+ AptConfig::new("profiles/apt.conf").expect("valid apt config"),
+ ["basesystem", ""],
+ std::iter::empty::<&str>(),
+ )
+ .is_err()
+ );
}
#[test]
@@ -82,11 +84,21 @@ fn build_plan_compiles_its_typed_package_request() {
.package_request("work/alt-controller", "profiles/apt.conf")
.expect("compile package request");
- assert_eq!(request.workdir(), std::path::Path::new("work/alt-controller"));
- assert_eq!(request.apt_config().as_path(), std::path::Path::new("profiles/apt.conf"));
+ assert_eq!(
+ request.workdir(),
+ std::path::Path::new("work/alt-controller")
+ );
+ assert_eq!(
+ request.apt_config().as_path(),
+ std::path::Path::new("profiles/apt.conf")
+ );
assert_eq!(request.packages()[0], "anacron");
assert!(request.packages().contains(&"apt".to_owned()));
assert!(request.packages().contains(&"libiec61850".to_owned()));
- assert!(request.packages().contains(&"make-initrd-multipath".to_owned()));
+ assert!(
+ request
+ .packages()
+ .contains(&"make-initrd-multipath".to_owned())
+ );
assert_eq!(request.selectors(), ["^kernel-(image|modules-())-(rt)$"]);
}
diff --git a/tests/rootfs_finalization.rs b/tests/rootfs_finalization.rs
index a450b7e..9e07436 100644
--- a/tests/rootfs_finalization.rs
+++ b/tests/rootfs_finalization.rs
@@ -10,7 +10,8 @@ fn finalization_copies_trees_generates_oem_recipe_and_enables_services() {
let fixture = tempdir().expect("fixture directory");
let source = fixture.path().join("overlay");
fs::create_dir_all(source.join("nested")).expect("create source tree");
- fs::write(source.join("nested/controller.conf"), "mode = controller\n").expect("write source file");
+ fs::write(source.join("nested/controller.conf"), "mode = controller\n")
+ .expect("write source file");
symlink("nested/controller.conf", source.join("controller.conf"))
.expect("create relative source symlink");
@@ -29,7 +30,8 @@ fn finalization_copies_trees_generates_oem_recipe_and_enables_services() {
"mode = controller\n"
);
assert_eq!(
- fs::read_link(rootfs.path().join("etc/controller/controller.conf")).expect("copied symlink"),
+ fs::read_link(rootfs.path().join("etc/controller/controller.conf"))
+ .expect("copied symlink"),
Path::new("nested/controller.conf")
);
assert_eq!(
@@ -94,11 +96,16 @@ fn finalization_is_idempotent_for_matching_symlinks() {
vec![ServiceName::new("chronyd.service").expect("valid service")],
);
- finalization.apply(rootfs.path()).expect("first finalization succeeds");
- let second = finalization.apply(rootfs.path()).expect("repeat finalization succeeds");
+ finalization
+ .apply(rootfs.path())
+ .expect("first finalization succeeds");
+ let second = finalization
+ .apply(rootfs.path())
+ .expect("repeat finalization succeeds");
assert_eq!(
- fs::read_link(rootfs.path().join("etc/controller/controller-link")).expect("copied symlink"),
+ fs::read_link(rootfs.path().join("etc/controller/controller-link"))
+ .expect("copied symlink"),
Path::new("controller.conf")
);
assert_eq!(
@@ -110,10 +117,12 @@ fn finalization_is_idempotent_for_matching_symlinks() {
.expect("enabled service"),
Path::new("/usr/lib/systemd/system/chronyd.service")
);
- assert!(second
- .created_paths()
- .iter()
- .any(|path| path == "etc/controller/controller-link"));
+ assert!(
+ second
+ .created_paths()
+ .iter()
+ .any(|path| path == "etc/controller/controller-link")
+ );
}
#[test]
@@ -121,29 +130,43 @@ fn finalization_replaces_a_destination_symlink_instead_of_following_it() {
let fixture = tempdir().expect("fixture directory");
let source = fixture.path().join("overlay");
fs::create_dir_all(&source).expect("create source tree");
- fs::write(source.join("controller.conf"), "new controller configuration\n")
- .expect("write source file");
+ fs::write(
+ source.join("controller.conf"),
+ "new controller configuration\n",
+ )
+ .expect("write source file");
let rootfs = tempdir().expect("rootfs directory");
let outside = fixture.path().join("outside.conf");
fs::write(&outside, "must not change\n").expect("write outside file");
fs::create_dir_all(rootfs.path().join("etc/controller")).expect("create destination directory");
- symlink(&outside, rootfs.path().join("etc/controller/controller.conf"))
- .expect("create destination symlink");
+ symlink(
+ &outside,
+ rootfs.path().join("etc/controller/controller.conf"),
+ )
+ .expect("create destination symlink");
let finalization = RootfsFinalization::new(
vec![CopyTree::new(&source, "etc/controller").expect("valid copy destination")],
InitrdOem::new(std::iter::empty::<&str>(), std::iter::empty::<&str>()),
vec![],
);
- finalization.apply(rootfs.path()).expect("finalization replaces symlink safely");
+ finalization
+ .apply(rootfs.path())
+ .expect("finalization replaces symlink safely");
- assert_eq!(fs::read_to_string(&outside).expect("read outside file"), "must not change\n");
- assert!(fs::symlink_metadata(rootfs.path().join("etc/controller/controller.conf"))
- .expect("inspect copied file")
- .file_type()
- .is_file());
assert_eq!(
- fs::read_to_string(rootfs.path().join("etc/controller/controller.conf")).expect("read copied file"),
+ fs::read_to_string(&outside).expect("read outside file"),
+ "must not change\n"
+ );
+ assert!(
+ fs::symlink_metadata(rootfs.path().join("etc/controller/controller.conf"))
+ .expect("inspect copied file")
+ .file_type()
+ .is_file()
+ );
+ assert_eq!(
+ fs::read_to_string(rootfs.path().join("etc/controller/controller.conf"))
+ .expect("read copied file"),
"new controller configuration\n"
);
}
diff --git a/tests/stage_graph.rs b/tests/stage_graph.rs
index cd6fc93..13a297d 100644
--- a/tests/stage_graph.rs
+++ b/tests/stage_graph.rs
@@ -34,7 +34,10 @@ fn stage_graph_orders_artifact_producers_before_consumers() {
Stage::Manifest,
]
);
- assert_eq!(plan.dependencies(Stage::Package), &[Stage::FinalizeRootfs, Stage::BuildInitramfs]);
+ assert_eq!(
+ plan.dependencies(Stage::Package),
+ &[Stage::FinalizeRootfs, Stage::BuildInitramfs]
+ );
assert_eq!(plan.dependencies(Stage::BuildInitramfs), &[Stage::Install]);
assert_eq!(plan.dependencies(Stage::Manifest), &[Stage::Package]);
}