Skip to content

Commit 9b0408f

Browse files
committed
feat(client-cli): implement unpack of Cardano node distribution
1 parent 9e1f5a6 commit 9b0408f

File tree

8 files changed

+511
-0
lines changed

8 files changed

+511
-0
lines changed

Cargo.lock

Lines changed: 144 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

mithril-client-cli/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ chrono = { workspace = true }
3131
clap = { workspace = true }
3232
cli-table = "0.5.0"
3333
config = { workspace = true }
34+
flate2 = "1.1.1"
3435
fs2 = "0.4.3"
3536
futures = "0.3.31"
3637
human_bytes = { version = "0.4.3", features = ["fast"] }
@@ -54,8 +55,10 @@ slog = { workspace = true, features = [
5455
slog-async = { workspace = true }
5556
slog-bunyan = { workspace = true }
5657
slog-term = { workspace = true }
58+
tar = "0.4.44"
5759
thiserror = { workspace = true }
5860
tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
61+
zip = "4.0.0"
5962

6063
[dev-dependencies]
6164
mithril-common = { path = "../mithril-common", features = ["test_tools"] }
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
mod tar_gz_unpacker;
2+
mod zip_unpacker;
3+
4+
pub use tar_gz_unpacker::*;
5+
pub use zip_unpacker::*;
6+
7+
use mithril_client::MithrilResult;
8+
use std::path::Path;
9+
10+
#[cfg_attr(test, mockall::automock)]
11+
pub trait ArchiveFormat {
12+
fn supports(&self, archive_path: &Path) -> bool;
13+
14+
fn unpack(&self, archive_path: &Path, unpack_dir: &Path) -> MithrilResult<()>;
15+
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
use std::{fs::File, path::Path};
2+
3+
use anyhow::Context;
4+
use flate2::read::GzDecoder;
5+
use tar::Archive;
6+
7+
use mithril_client::MithrilResult;
8+
9+
use super::ArchiveFormat;
10+
11+
#[derive(Debug, Eq, PartialEq)]
12+
pub struct TarGzUnpacker;
13+
14+
impl ArchiveFormat for TarGzUnpacker {
15+
fn unpack(&self, archive_path: &Path, unpack_dir: &Path) -> MithrilResult<()> {
16+
let archive = File::open(archive_path)
17+
.with_context(|| format!("Could not open archive file '{}'", archive_path.display()))?;
18+
let gzip_decoder = GzDecoder::new(archive);
19+
let mut file_archive = Archive::new(gzip_decoder);
20+
file_archive.unpack(unpack_dir).with_context(|| {
21+
format!(
22+
"Could not unpack '{}' with 'Gzip' to directory '{}'",
23+
archive_path.display(),
24+
unpack_dir.display()
25+
)
26+
})?;
27+
28+
Ok(())
29+
}
30+
31+
fn supports(&self, path: &Path) -> bool {
32+
path.extension().and_then(|e| e.to_str()) == Some("gz")
33+
}
34+
}
35+
36+
#[cfg(test)]
37+
mod tests {
38+
use std::fs::{self, File};
39+
40+
use flate2::{write::GzEncoder, Compression};
41+
use tar::{Builder, Header};
42+
43+
use mithril_common::{assert_dir_eq, temp_dir_create};
44+
45+
use super::*;
46+
47+
#[test]
48+
fn unpack_tar_archive_extracts_all_files() {
49+
let temp_dir = temp_dir_create!();
50+
let archive_path = temp_dir.join("archive.tar.gz");
51+
52+
{
53+
let tar_gz_file = File::create(&archive_path).unwrap();
54+
let encoder = GzEncoder::new(tar_gz_file, Compression::default());
55+
let mut tar_builder = Builder::new(encoder);
56+
57+
let content = b"root content";
58+
let mut header = Header::new_gnu();
59+
header.set_size(content.len() as u64);
60+
header.set_cksum();
61+
tar_builder
62+
.append_data(&mut header, "root.txt", &content[..])
63+
.unwrap();
64+
65+
let content = b"nested content";
66+
let mut header = Header::new_gnu();
67+
header.set_size(content.len() as u64);
68+
header.set_cksum();
69+
tar_builder
70+
.append_data(&mut header, "nested/dir/nested-file.txt", &content[..])
71+
.unwrap();
72+
73+
tar_builder.finish().unwrap();
74+
}
75+
76+
TarGzUnpacker.unpack(&archive_path, &temp_dir).unwrap();
77+
78+
assert_dir_eq! {
79+
&temp_dir,
80+
"* nested/
81+
** dir/
82+
*** nested-file.txt
83+
* archive.tar.gz
84+
* root.txt"
85+
};
86+
87+
let root_file_content = fs::read_to_string(temp_dir.join("root.txt")).unwrap();
88+
assert_eq!(root_file_content, "root content");
89+
90+
let nested_file_content =
91+
fs::read_to_string(temp_dir.join("nested/dir/nested-file.txt")).unwrap();
92+
assert_eq!(nested_file_content, "nested content");
93+
}
94+
95+
#[test]
96+
fn supported_file_extension() {
97+
assert!(TarGzUnpacker.supports(Path::new("archive.tar.gz")));
98+
assert!(TarGzUnpacker.supports(Path::new("archive.gz")));
99+
assert!(!TarGzUnpacker.supports(Path::new("archive.tar")));
100+
assert!(!TarGzUnpacker.supports(Path::new("archive.whatever")));
101+
}
102+
}

0 commit comments

Comments
 (0)