Skip to content

Commit 4b92a70

Browse files
committed
feat(client-cli): implement download of Cardano node distribution to retrieve the snapshot-converter binary
1 parent 1d6fe27 commit 4b92a70

File tree

12 files changed

+576
-6
lines changed

12 files changed

+576
-6
lines changed

Cargo.lock

Lines changed: 2 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: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,13 @@ indicatif = { version = "0.17.11", features = ["tokio"] }
3838
mithril-cli-helper = { path = "../internal/mithril-cli-helper" }
3939
mithril-client = { path = "../mithril-client", features = ["fs", "unstable"] }
4040
mithril-doc = { path = "../internal/mithril-doc" }
41+
reqwest = { workspace = true, features = [
42+
"default",
43+
"gzip",
44+
"zstd",
45+
"deflate",
46+
"brotli"
47+
] }
4148
serde = { workspace = true }
4249
serde_json = { workspace = true }
4350
slog = { workspace = true, features = [
@@ -52,3 +59,4 @@ tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
5259

5360
[dev-dependencies]
5461
mithril-common = { path = "../mithril-common", features = ["test_tools"] }
62+
mockall = { workspace = true }

mithril-client-cli/src/commands/tools/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@
55
66
mod snapshot_converter;
77

8-
use mithril_client::MithrilResult;
98
pub use snapshot_converter::*;
109

1110
use clap::Subcommand;
11+
use mithril_client::MithrilResult;
1212

1313
/// Tools commands
1414
#[derive(Subcommand, Debug, Clone)]
Lines changed: 235 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,245 @@
1-
use clap::Parser;
1+
use std::{
2+
env, fmt,
3+
path::{Path, PathBuf},
4+
};
5+
6+
use anyhow::{anyhow, Context};
7+
use clap::{Parser, ValueEnum};
28

39
use mithril_client::MithrilResult;
410

11+
use crate::utils::{
12+
GitHubReleaseRetriever, HttpDownloader, ReqwestGitHubApiClient, ReqwestHttpDownloader,
13+
};
14+
15+
const GITHUB_ORGANIZATION: &str = "IntersectMBO";
16+
const GITHUB_REPOSITORY: &str = "cardano-node";
17+
18+
const LATEST_DISTRIBUTION_TAG: &str = "latest";
19+
const PRERELEASE_DISTRIBUTION_TAG: &str = "prerelease";
20+
21+
const CARDANO_DISTRIBUTION_TEMP_DIR: &str = "cardano-node-distribution-tmp";
22+
23+
#[derive(Debug, Clone, ValueEnum)]
24+
enum UTxOHDFlavor {
25+
#[clap(name = "Legacy")]
26+
Legacy,
27+
#[clap(name = "LMDB")]
28+
Lmdb,
29+
}
30+
31+
impl fmt::Display for UTxOHDFlavor {
32+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
33+
match self {
34+
Self::Legacy => write!(f, "Legacy"),
35+
Self::Lmdb => write!(f, "LMDB"),
36+
}
37+
}
38+
}
39+
40+
/// Clap command to convert a restored `InMemory` Mithril snapshot to another flavor.
541
#[derive(Parser, Debug, Clone)]
6-
pub struct SnapshotConverterCommand {}
42+
pub struct SnapshotConverterCommand {
43+
/// Path to the Cardano node database directory.
44+
#[clap(long)]
45+
db_directory: PathBuf,
46+
47+
/// Cardano node version of the Mithril signed snapshot.
48+
///
49+
/// `latest` and `prerelease` are also supported to download the latest or preprelease distribution.
50+
#[clap(long)]
51+
cardano_node_version: String,
52+
53+
/// UTxO-HD flavor to convert the ledger snapshot to.
54+
#[clap(long)]
55+
utxo_hd_flavor: UTxOHDFlavor,
56+
}
757

858
impl SnapshotConverterCommand {
959
/// Main command execution
1060
pub async fn execute(&self) -> MithrilResult<()> {
11-
todo!()
61+
let distribution_temp_dir = self.db_directory.join(CARDANO_DISTRIBUTION_TEMP_DIR);
62+
std::fs::create_dir(&distribution_temp_dir).with_context(|| {
63+
format!(
64+
"Failed to create directory: {}",
65+
distribution_temp_dir.display()
66+
)
67+
})?;
68+
69+
let archive_path = Self::download_cardano_node_distribution(
70+
ReqwestGitHubApiClient::new()?,
71+
ReqwestHttpDownloader::new()?,
72+
&self.cardano_node_version,
73+
&distribution_temp_dir,
74+
)
75+
.await
76+
.with_context(|| {
77+
"Failed to download 'snapshot-converter' binary from Cardano node distribution"
78+
})?;
79+
80+
Ok(())
81+
}
82+
83+
async fn download_cardano_node_distribution(
84+
github_api_client: impl GitHubReleaseRetriever,
85+
http_downloader: impl HttpDownloader,
86+
tag: &str,
87+
target_dir: &Path,
88+
) -> MithrilResult<PathBuf> {
89+
println!(
90+
"Downloading Cardano node distribution for tag: '{}'...",
91+
tag
92+
);
93+
let release = match tag {
94+
LATEST_DISTRIBUTION_TAG => github_api_client
95+
.get_latest_release(GITHUB_ORGANIZATION, GITHUB_REPOSITORY)
96+
.await
97+
.with_context(|| "Failed to get latest release")?,
98+
PRERELEASE_DISTRIBUTION_TAG => github_api_client
99+
.get_prerelease(GITHUB_ORGANIZATION, GITHUB_REPOSITORY)
100+
.await
101+
.with_context(|| "Failed to get prerelease")?,
102+
_ => github_api_client
103+
.get_release_by_tag(GITHUB_ORGANIZATION, GITHUB_REPOSITORY, tag)
104+
.await
105+
.with_context(|| format!("Failed to get release by tag: {}", tag))?,
106+
};
107+
108+
let asset = release
109+
.get_asset_for_os(env::consts::OS)?
110+
.ok_or_else(|| anyhow!("No asset found for platform: {}", env::consts::OS))
111+
.with_context(|| {
112+
format!(
113+
"Failed to find asset for current platform: {}",
114+
env::consts::OS
115+
)
116+
})?;
117+
118+
let archive_path = http_downloader
119+
.download_file(asset.browser_download_url.parse()?, target_dir, &asset.name)
120+
.await?;
121+
122+
println!(
123+
"Distribution downloaded successfully. Archive location: {}",
124+
archive_path.display()
125+
);
126+
127+
Ok(archive_path)
128+
}
129+
}
130+
131+
#[cfg(test)]
132+
mod tests {
133+
use mockall::predicate::eq;
134+
use reqwest::Url;
135+
136+
use mithril_common::temp_dir_create;
137+
138+
use crate::utils::{GitHubRelease, MockGitHubReleaseRetriever, MockHttpDownloader};
139+
140+
use super::*;
141+
142+
#[tokio::test]
143+
async fn call_get_latest_release_with_latest_tag() {
144+
let temp_dir = temp_dir_create!();
145+
let release = GitHubRelease::dummy_with_all_supported_assets();
146+
let asset = release.get_asset_for_os(env::consts::OS).unwrap().unwrap();
147+
148+
let cloned_release = release.clone();
149+
let mut github_api_client = MockGitHubReleaseRetriever::new();
150+
github_api_client
151+
.expect_get_latest_release()
152+
.with(eq(GITHUB_ORGANIZATION), eq(GITHUB_REPOSITORY))
153+
.returning(move |_, _| Ok(cloned_release.clone()));
154+
155+
let mut http_downloader = MockHttpDownloader::new();
156+
http_downloader
157+
.expect_download_file()
158+
.with(
159+
eq(Url::parse(&asset.browser_download_url).unwrap()),
160+
eq(temp_dir.clone()),
161+
eq(asset.name.clone()),
162+
)
163+
.returning(|_, _, _| Ok(PathBuf::new()));
164+
165+
SnapshotConverterCommand::download_cardano_node_distribution(
166+
github_api_client,
167+
http_downloader,
168+
LATEST_DISTRIBUTION_TAG,
169+
&temp_dir,
170+
)
171+
.await
172+
.unwrap();
173+
}
174+
175+
#[tokio::test]
176+
async fn call_get_prerelease_with_prerelease_tag() {
177+
let temp_dir = temp_dir_create!();
178+
let release = GitHubRelease::dummy_with_all_supported_assets();
179+
let asset = release.get_asset_for_os(env::consts::OS).unwrap().unwrap();
180+
181+
let cloned_release = release.clone();
182+
let mut github_api_client = MockGitHubReleaseRetriever::new();
183+
github_api_client
184+
.expect_get_prerelease()
185+
.with(eq(GITHUB_ORGANIZATION), eq(GITHUB_REPOSITORY))
186+
.returning(move |_, _| Ok(cloned_release.clone()));
187+
188+
let mut http_downloader = MockHttpDownloader::new();
189+
http_downloader
190+
.expect_download_file()
191+
.with(
192+
eq(Url::parse(&asset.browser_download_url).unwrap()),
193+
eq(temp_dir.clone()),
194+
eq(asset.name.clone()),
195+
)
196+
.returning(|_, _, _| Ok(PathBuf::new()));
197+
198+
SnapshotConverterCommand::download_cardano_node_distribution(
199+
github_api_client,
200+
http_downloader,
201+
PRERELEASE_DISTRIBUTION_TAG,
202+
&temp_dir,
203+
)
204+
.await
205+
.unwrap();
206+
}
207+
208+
#[tokio::test]
209+
async fn call_get_release_by_tag_with_specific_cardano_node_version() {
210+
let cardano_node_version = "10.3.1";
211+
let temp_dir = temp_dir_create!();
212+
let release = GitHubRelease::dummy_with_all_supported_assets();
213+
let asset = release.get_asset_for_os(env::consts::OS).unwrap().unwrap();
214+
215+
let cloned_release = release.clone();
216+
let mut github_api_client = MockGitHubReleaseRetriever::new();
217+
github_api_client
218+
.expect_get_release_by_tag()
219+
.with(
220+
eq(GITHUB_ORGANIZATION),
221+
eq(GITHUB_REPOSITORY),
222+
eq(cardano_node_version),
223+
)
224+
.returning(move |_, _, _| Ok(cloned_release.clone()));
225+
226+
let mut http_downloader = MockHttpDownloader::new();
227+
http_downloader
228+
.expect_download_file()
229+
.with(
230+
eq(Url::parse(&asset.browser_download_url).unwrap()),
231+
eq(temp_dir.clone()),
232+
eq(asset.name.clone()),
233+
)
234+
.returning(|_, _, _| Ok(PathBuf::new()));
235+
236+
SnapshotConverterCommand::download_cardano_node_distribution(
237+
github_api_client,
238+
http_downloader,
239+
cardano_node_version,
240+
&temp_dir,
241+
)
242+
.await
243+
.unwrap();
12244
}
13245
}

mithril-client-cli/src/main.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -298,8 +298,6 @@ mod tests {
298298
"snapshot-converter",
299299
"--db-directory",
300300
"whatever",
301-
"--cardano-network",
302-
"preview",
303301
"--cardano-node-version",
304302
"1.2.3",
305303
"--utxo-hd-flavor",

0 commit comments

Comments
 (0)