feat: download and install k3d latest release
- Implemented functionality to fetch the latest k3d release tag from GitHub. - Added logic to determine the appropriate binary URL based on the current platform. - Implemented downloading and saving the binary to a specified directory. - Included unit tests to verify the download and installation process. - Added a `K3D_BIN_FILE_NAME` constant for clarity. - Added logging for better debugging.
This commit is contained in:
154
k3d/src/lib.rs
154
k3d/src/lib.rs
@@ -1,12 +1,164 @@
|
||||
mod downloadable_asset;
|
||||
use downloadable_asset::*;
|
||||
|
||||
use log::{debug, info};
|
||||
use std::path::PathBuf;
|
||||
|
||||
const K3D_BIN_FILE_NAME: &str = "k3d";
|
||||
|
||||
pub struct K3d {
|
||||
base_dir: PathBuf,
|
||||
}
|
||||
|
||||
impl K3d {
|
||||
pub async fn download_latest_release(&self) -> Result<PathBuf, String> {
|
||||
pub fn new(base_dir: PathBuf) -> Self {
|
||||
Self { base_dir }
|
||||
}
|
||||
|
||||
async fn get_binary_for_current_platform(
|
||||
&self,
|
||||
latest_release: octocrab::models::repos::Release,
|
||||
) -> DownloadableAsset {
|
||||
let os = std::env::consts::OS;
|
||||
let arch = std::env::consts::ARCH;
|
||||
|
||||
debug!("Detecting platform: OS={}, ARCH={}", os, arch);
|
||||
|
||||
// 2. Construct the binary name pattern based on platform
|
||||
let binary_pattern = match (os, arch) {
|
||||
("linux", "x86") => "k3d-linux-386",
|
||||
("linux", "x86_64") => "k3d-linux-amd64",
|
||||
("linux", "arm") => "k3d-linux-arm",
|
||||
("linux", "aarch64") => "k3d-linux-arm64",
|
||||
("windows", "x86_64") => "k3d-windows-amd64.exe",
|
||||
("macos", "x86_64") => "k3d-darwin-amd64",
|
||||
("macos", "aarch64") => "k3d-darwin-arm64",
|
||||
_ => panic!("Unsupported platform: {}-{}", os, arch),
|
||||
};
|
||||
|
||||
debug!("Looking for binary matching pattern: {}", binary_pattern);
|
||||
|
||||
// 3. Find the matching binary in release assets
|
||||
let binary_asset = latest_release
|
||||
.assets
|
||||
.iter()
|
||||
.find(|asset| asset.name == binary_pattern)
|
||||
.unwrap_or_else(|| panic!("No matching binary found for {}", binary_pattern));
|
||||
|
||||
let binary_url = binary_asset.browser_download_url.clone();
|
||||
|
||||
// 4. Find and parse the checksums file
|
||||
let checksums_asset = latest_release
|
||||
.assets
|
||||
.iter()
|
||||
.find(|asset| asset.name == "checksums.txt")
|
||||
.expect("Checksums file not found in release assets");
|
||||
|
||||
// 5. Download and parse checksums file
|
||||
let checksums_url = checksums_asset.browser_download_url.clone();
|
||||
|
||||
let body = reqwest::get(checksums_url)
|
||||
.await
|
||||
.unwrap()
|
||||
.text()
|
||||
.await
|
||||
.unwrap();
|
||||
println!("body: {body}");
|
||||
|
||||
// 6. Find the checksum for our binary
|
||||
let checksum = body
|
||||
.lines()
|
||||
.find_map(|line| {
|
||||
if line.ends_with(&binary_pattern) {
|
||||
Some(line.split_whitespace().next().unwrap_or("").to_string())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|| panic!("Checksum not found for {}", binary_pattern));
|
||||
|
||||
debug!("Found binary at {} with checksum {}", binary_url, checksum);
|
||||
|
||||
DownloadableAsset {
|
||||
url: binary_url,
|
||||
file_name: K3D_BIN_FILE_NAME.to_string(),
|
||||
checksum,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn download_latest_release(&self) -> Result<PathBuf, String> {
|
||||
let latest_release = self.get_latest_release_tag().await.unwrap();
|
||||
|
||||
let release_binary = self.get_binary_for_current_platform(latest_release).await;
|
||||
info!("Foudn K3d binary to install : {release_binary:#?}");
|
||||
release_binary.download_to_path(self.base_dir.clone()).await
|
||||
}
|
||||
|
||||
// TODO : Make sure this will only find actual released versions, no prereleases or test
|
||||
// builds
|
||||
pub async fn get_latest_release_tag(&self) -> Result<octocrab::models::repos::Release, String> {
|
||||
let octo = octocrab::instance();
|
||||
let latest_release = octo
|
||||
.repos("k3d-io", "k3d")
|
||||
.releases()
|
||||
.get_latest()
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
// debug!("Got k3d releases {releases:#?}");
|
||||
println!("Got k3d first releases {latest_release:#?}");
|
||||
|
||||
Ok(latest_release)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use regex::Regex;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::{K3d, K3D_BIN_FILE_NAME};
|
||||
|
||||
#[tokio::test]
|
||||
async fn k3d_latest_release_should_get_latest() {
|
||||
let dir = get_clean_test_directory();
|
||||
|
||||
assert_eq!(dir.join(K3D_BIN_FILE_NAME).exists(), false);
|
||||
|
||||
let k3d = K3d::new(dir.clone());
|
||||
let latest_release = k3d.get_latest_release_tag().await.unwrap();
|
||||
|
||||
let tag_regex = Regex::new(r"^v\d+\.\d+\.\d+$").unwrap();
|
||||
assert!(tag_regex.is_match(&latest_release.tag_name));
|
||||
assert!(!latest_release.tag_name.is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn k3d_download_latest_release_should_get_latest_bin() {
|
||||
let dir = get_clean_test_directory();
|
||||
|
||||
assert_eq!(dir.join(K3D_BIN_FILE_NAME).exists(), false);
|
||||
|
||||
let k3d = K3d::new(dir.clone());
|
||||
let bin_file_path = k3d.download_latest_release().await.unwrap();
|
||||
assert_eq!(bin_file_path, dir.join(K3D_BIN_FILE_NAME));
|
||||
assert_eq!(dir.join(K3D_BIN_FILE_NAME).exists(), true);
|
||||
}
|
||||
|
||||
fn get_clean_test_directory() -> PathBuf {
|
||||
let dir = PathBuf::from("/tmp/harmony-k3d-test-dir");
|
||||
|
||||
if dir.exists() {
|
||||
if let Err(e) = std::fs::remove_dir_all(&dir) {
|
||||
// TODO sometimes this fails because of the race when running multiple tests at
|
||||
// once
|
||||
panic!("Failed to clean up test directory: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
if let Err(e) = std::fs::create_dir_all(&dir) {
|
||||
panic!("Failed to create test directory: {}", e);
|
||||
}
|
||||
|
||||
dir
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user