Skip to content

Commit 9e1f5a6

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

File tree

11 files changed

+568
-4
lines changed

11 files changed

+568
-4
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: 227 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,237 @@
1-
use clap::Parser;
1+
// TODO:
2+
// - Add logs.
3+
// - Remove the temporary directory in all cases (error or success).
4+
use std::path::Path;
5+
use std::{env, fmt, path::PathBuf};
6+
7+
use anyhow::{anyhow, Context};
8+
use clap::{Parser, ValueEnum};
29

310
use mithril_client::MithrilResult;
411

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

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

0 commit comments

Comments
 (0)