summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/archive.rs44
-rw-r--r--tests/archive.rs17
2 files changed, 52 insertions, 9 deletions
diff --git a/src/archive.rs b/src/archive.rs
index 70392fa..9a899bf 100644
--- a/src/archive.rs
+++ b/src/archive.rs
@@ -1,7 +1,7 @@
use std::collections::BTreeMap;
-use std::fs::{self, File};
+use std::fs::{self, File, OpenOptions};
use std::os::unix::fs::MetadataExt;
-use std::path::Path;
+use std::path::{Path, PathBuf};
use anyhow::{bail, Context, Result};
use tar::{Builder, EntryType, Header};
@@ -26,16 +26,42 @@ impl NativeTarWriter {
}
let output = output.as_ref();
- let file = File::create(output).with_context(|| format!("create archive {}", output.display()))?;
- let mut archive = Builder::new(file);
- append_tree(&mut archive, rootfs, Path::new(""), &mut BTreeMap::new())?;
- archive
- .finish()
- .with_context(|| format!("finish archive {}", output.display()))?;
- Ok(())
+ let temporary = temporary_output_path(output)?;
+ let result = (|| {
+ let file = OpenOptions::new()
+ .write(true)
+ .create_new(true)
+ .open(&temporary)
+ .with_context(|| format!("create temporary archive {}", temporary.display()))?;
+ let mut archive = Builder::new(file);
+ append_tree(&mut archive, rootfs, Path::new(""), &mut BTreeMap::new())?;
+ archive
+ .finish()
+ .with_context(|| format!("finish archive {}", temporary.display()))?;
+ fs::rename(&temporary, output).with_context(|| {
+ format!(
+ "publish completed archive {} as {}",
+ temporary.display(),
+ output.display()
+ )
+ })
+ })();
+ if result.is_err() {
+ let _ = fs::remove_file(&temporary);
+ }
+ 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 mut temporary_name = name.to_os_string();
+ temporary_name.push(".partial");
+ Ok(output.with_file_name(temporary_name))
+}
+
fn append_tree(
archive: &mut Builder<File>,
rootfs: &Path,
diff --git a/tests/archive.rs b/tests/archive.rs
index e31b65a..da3e66a 100644
--- a/tests/archive.rs
+++ b/tests/archive.rs
@@ -1,5 +1,6 @@
use std::fs;
use std::os::unix::fs::symlink;
+use std::os::unix::net::UnixListener;
use alt_controller_image::archive::NativeTarWriter;
use tar::Archive;
@@ -105,3 +106,19 @@ fn rejects_a_missing_rootfs() {
.expect_err("missing rootfs must fail");
assert!(error.to_string().contains("rootfs is not a directory"));
}
+
+#[test]
+fn removes_partial_output_when_an_unsupported_entry_stops_packaging() {
+ let fixture = tempdir().expect("fixture directory");
+ let rootfs = fixture.path().join("rootfs");
+ fs::create_dir(&rootfs).expect("create rootfs");
+ let _socket = UnixListener::bind(rootfs.join("image-builder.sock")).expect("create socket");
+ let artifact = fixture.path().join("image.tar");
+
+ let error = NativeTarWriter::new()
+ .write(&rootfs, &artifact)
+ .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");
+}