Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support node manager releases #9

Merged
merged 1 commit into from
Dec 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,18 @@ pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, Error)]
#[allow(missing_docs)]
pub enum Error {
#[error(transparent)]
DateTimeParseError(#[from] chrono::ParseError),
#[error("Cannot parse file name from the URL")]
CannotParseFilenameFromUrl,
#[error(transparent)]
DateTimeParseError(#[from] chrono::ParseError),
#[error("Could not convert API response header links to string")]
HeaderLinksToStrError,
#[error(transparent)]
Io(#[from] std::io::Error),
#[error("Latest release not found for {0}")]
LatestReleaseNotFound(String),
#[error("The Github API's latest release response document was not in the expected format")]
MalformedLatestReleaseResponse,
#[error("{0}")]
PlatformNotSupported(String),
#[error(transparent)]
Expand Down
49 changes: 48 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ const GITHUB_ORG_NAME: &str = "maidsafe";
const GITHUB_REPO_NAME: &str = "safe_network";
const SAFE_S3_BASE_URL: &str = "https://sn-cli.s3.eu-west-2.amazonaws.com";
const SAFENODE_S3_BASE_URL: &str = "https://sn-node.s3.eu-west-2.amazonaws.com";
const SAFENODE_MANAGER_S3_BASE_URL: &str = "https://sn-node-manager.s3.eu-west-2.amazonaws.com";
const SAFENODE_RPC_CLIENT_S3_BASE_URL: &str =
"https://sn-node-rpc-client.s3.eu-west-2.amazonaws.com";
const TESTNET_S3_BASE_URL: &str = "https://sn-testnet.s3.eu-west-2.amazonaws.com";
Expand All @@ -37,6 +38,7 @@ const TESTNET_S3_BASE_URL: &str = "https://sn-testnet.s3.eu-west-2.amazonaws.com
pub enum ReleaseType {
Safe,
Safenode,
SafenodeManager,
SafenodeRpcClient,
Testnet,
}
Expand All @@ -49,18 +51,32 @@ impl fmt::Display for ReleaseType {
match self {
ReleaseType::Safe => "safe",
ReleaseType::Safenode => "safenode",
ReleaseType::SafenodeManager => "safenode-manager",
ReleaseType::SafenodeRpcClient => "safenode_rpc_client",
ReleaseType::Testnet => "testnet",
}
)
}
}

impl ReleaseType {
pub fn get_repo_name(&self) -> String {
match &self {
ReleaseType::Safe
| ReleaseType::Safenode
| ReleaseType::SafenodeRpcClient
| ReleaseType::Testnet => "safe_network".to_string(),
ReleaseType::SafenodeManager => "sn-node-manager".to_string(),
}
}
}

lazy_static! {
static ref RELEASE_TYPE_CRATE_NAME_MAP: HashMap<ReleaseType, &'static str> = {
let mut m = HashMap::new();
m.insert(ReleaseType::Safe, "sn_cli");
m.insert(ReleaseType::Safenode, "sn_node");
m.insert(ReleaseType::SafenodeManager, "sn-node-manager");
m.insert(ReleaseType::SafenodeRpcClient, "sn_node_rpc_client");
m.insert(ReleaseType::Testnet, "sn_testnet");
m
Expand Down Expand Up @@ -135,6 +151,7 @@ impl dyn SafeReleaseRepositoryInterface {
github_api_base_url: GITHUB_API_URL.to_string(),
safe_base_url: SAFE_S3_BASE_URL.to_string(),
safenode_base_url: SAFENODE_S3_BASE_URL.to_string(),
safenode_manager_base_url: SAFENODE_MANAGER_S3_BASE_URL.to_string(),
safenode_rpc_client_base_url: SAFENODE_RPC_CLIENT_S3_BASE_URL.to_string(),
testnet_base_url: TESTNET_S3_BASE_URL.to_string(),
})
Expand All @@ -145,6 +162,7 @@ pub struct SafeReleaseRepository {
pub github_api_base_url: String,
pub safe_base_url: String,
pub safenode_base_url: String,
pub safenode_manager_base_url: String,
pub safenode_rpc_client_base_url: String,
pub testnet_base_url: String,
}
Expand All @@ -154,11 +172,33 @@ impl SafeReleaseRepository {
match release_type {
ReleaseType::Safe => self.safe_base_url.clone(),
ReleaseType::Safenode => self.safenode_base_url.clone(),
ReleaseType::SafenodeManager => self.safenode_manager_base_url.clone(),
ReleaseType::SafenodeRpcClient => self.safenode_rpc_client_base_url.clone(),
ReleaseType::Testnet => self.testnet_base_url.clone(),
}
}

async fn get_latest_release_tag(&self, release_type: &ReleaseType) -> Result<String> {
let client = Client::new();
let response = client
.get(format!(
"{}/repos/{}/{}/releases/latest",
self.github_api_base_url,
GITHUB_ORG_NAME,
release_type.get_repo_name()
))
.header("User-Agent", "request")
.send()
.await?;

let latest_release = response.json::<Value>().await?;
if let Some(Value::String(tag_name)) = latest_release.get("tag_name") {
return Ok(tag_name.trim_start_matches('v').to_string());
}

Err(Error::MalformedLatestReleaseResponse)
}

async fn get_releases_page(&self, page: u32, per_page: u32) -> Result<Response> {
let client = Client::new();
let response = client
Expand Down Expand Up @@ -225,7 +265,10 @@ impl SafeReleaseRepository {

#[async_trait]
impl SafeReleaseRepositoryInterface for SafeReleaseRepository {
/// Gets the latest version for a specified binary in the `safe_network` repository.
/// Gets the latest version for a specified binary.
///
/// If we are looking for a node manager release, this is not a workspace repo, so we can
/// simply use the latest release API. Otherwise, we will query the `safe_network` repo.
///
/// Each release in the repository is checked, starting from the most recent. The `safe_network`
/// repository is a workspace to which many binaries are released, so it's not possible to use the
Expand All @@ -252,6 +295,10 @@ impl SafeReleaseRepositoryInterface for SafeReleaseRepository {
/// - The received JSON data from the API is not as expected
/// - No releases are found that match the specified `ReleaseType`
async fn get_latest_version(&self, release_type: &ReleaseType) -> Result<String> {
if *release_type == ReleaseType::SafenodeManager {
return self.get_latest_release_tag(release_type).await;
}

let mut page = 1;
let per_page = 100;
let mut latest_release: Option<(String, DateTime<Utc>)> = None;
Expand Down
71 changes: 71 additions & 0 deletions tests/test_download_from_s3.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use sn_releases::{ArchiveType, Platform, ReleaseType, SafeReleaseRepositoryInter

const SAFE_VERSION: &str = "0.83.51";
const SAFENODE_VERSION: &str = "0.93.7";
const SAFENODE_MANAGER_VERSION: &str = "0.1.8";
const SAFENODE_RPC_CLIENT_VERSION: &str = "0.1.40";
const TESTNET_VERSION: &str = "0.2.213";

Expand Down Expand Up @@ -50,6 +51,7 @@ async fn download_and_extract(
let binary_name = match release_type {
ReleaseType::Safe => "safe",
ReleaseType::Safenode => "safenode",
ReleaseType::SafenodeManager => "safenode-manager",
ReleaseType::SafenodeRpcClient => "safenode_rpc_client",
ReleaseType::Testnet => "testnet",
};
Expand Down Expand Up @@ -372,3 +374,72 @@ async fn should_download_and_extract_safenode_rpc_client_for_windows() {
)
.await;
}

///
/// Node Manager Tests
///
#[tokio::test]
async fn should_download_and_extract_safenode_manager_for_linux_musl() {
download_and_extract(
&ReleaseType::SafenodeManager,
SAFENODE_MANAGER_VERSION,
&Platform::LinuxMusl,
&ArchiveType::TarGz,
)
.await;
}

#[tokio::test]
async fn should_download_and_extract_safenode_manager_for_linux_musl_aarch64() {
download_and_extract(
&ReleaseType::SafenodeManager,
SAFENODE_MANAGER_VERSION,
&Platform::LinuxMuslAarch64,
&ArchiveType::TarGz,
)
.await;
}

#[tokio::test]
async fn should_download_and_extract_safenode_manager_for_linux_musl_arm() {
download_and_extract(
&ReleaseType::SafenodeManager,
SAFENODE_MANAGER_VERSION,
&Platform::LinuxMuslArm,
&ArchiveType::TarGz,
)
.await;
}

#[tokio::test]
async fn should_download_and_extract_safenode_manager_for_linux_musl_arm_v7() {
download_and_extract(
&ReleaseType::SafenodeManager,
SAFENODE_MANAGER_VERSION,
&Platform::LinuxMuslArmV7,
&ArchiveType::TarGz,
)
.await;
}

#[tokio::test]
async fn should_download_and_extract_safenode_manager_for_macos() {
download_and_extract(
&ReleaseType::SafenodeManager,
SAFENODE_MANAGER_VERSION,
&Platform::MacOs,
&ArchiveType::TarGz,
)
.await;
}

#[tokio::test]
async fn should_download_and_extract_safenode_manager_for_windows() {
download_and_extract(
&ReleaseType::SafenodeManager,
SAFENODE_MANAGER_VERSION,
&Platform::Windows,
&ArchiveType::Zip,
)
.await;
}
11 changes: 11 additions & 0 deletions tests/test_get_latest_version.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,17 @@ async fn should_get_latest_version_of_safenode_rpc_client() {
assert!(valid_semver_format(&version));
}

#[tokio::test]
async fn should_get_latest_version_of_safenode_manager() {
let release_type = ReleaseType::SafenodeManager;
let release_repo = <dyn SafeReleaseRepositoryInterface>::default_config();
let version = release_repo
.get_latest_version(&release_type)
.await
.unwrap();
assert!(valid_semver_format(&version));
}

#[tokio::test]
async fn should_get_latest_version_of_testnet() {
let release_type = ReleaseType::Testnet;
Expand Down
Loading