blueprint_manager/sdk/
utils.rs

1use crate::protocols::resolver::NativeGithubMetadata;
2use gadget_sdk::{info, warn};
3use sha2::Digest;
4use std::path::Path;
5use std::string::FromUtf8Error;
6use std::sync::atomic::{AtomicBool, Ordering};
7use std::sync::Arc;
8use tangle_subxt::tangle_testnet_runtime::api::runtime_types::tangle_primitives::services::field::BoundedString;
9use tangle_subxt::tangle_testnet_runtime::api::runtime_types::tangle_primitives::services::{
10    GadgetBinary, GithubFetcher,
11};
12
13pub fn github_fetcher_to_native_github_metadata(
14    gh: &GithubFetcher,
15    blueprint_id: u64,
16) -> NativeGithubMetadata {
17    let owner = bytes_to_utf8_string(gh.owner.0 .0.clone()).expect("Should be valid");
18    let repo = bytes_to_utf8_string(gh.repo.0 .0.clone()).expect("Should be valid");
19    let tag = bytes_to_utf8_string(gh.tag.0 .0.clone()).expect("Should be valid");
20    let git = format!("https://github.com/{owner}/{repo}");
21
22    NativeGithubMetadata {
23        fetcher: gh.clone(),
24        git,
25        tag,
26        repo,
27        owner,
28        gadget_binaries: gh.binaries.0.clone(),
29        blueprint_id,
30    }
31}
32
33pub fn bounded_string_to_string(string: BoundedString) -> Result<String, FromUtf8Error> {
34    let bytes: &Vec<u8> = &string.0 .0;
35    String::from_utf8(bytes.clone())
36}
37
38pub fn hash_bytes_to_hex<T: AsRef<[u8]>>(input: T) -> String {
39    let mut hasher = sha2::Sha256::default();
40    hasher.update(input);
41    hex::encode(hasher.finalize())
42}
43
44pub async fn valid_file_exists(path: &str, expected_hash: &str) -> bool {
45    // The hash is sha3_256 of the binary
46    if let Ok(file) = gadget_io::tokio::fs::read(path).await {
47        // Compute the SHA3-256
48        let retrieved_bytes = hash_bytes_to_hex(file);
49        expected_hash == retrieved_bytes.as_str()
50    } else {
51        false
52    }
53}
54
55pub fn get_formatted_os_string() -> String {
56    let os = std::env::consts::OS;
57
58    match os {
59        "macos" => "apple-darwin".to_string(),
60        "windows" => "pc-windows-msvc".to_string(),
61        "linux" => "unknown-linux-gnu".to_string(),
62        _ => os.to_string(),
63    }
64}
65
66pub fn get_download_url(binary: &GadgetBinary, fetcher: &GithubFetcher) -> String {
67    let os = get_formatted_os_string();
68    let ext = if os == "windows" { ".exe" } else { "" };
69    let owner = String::from_utf8(fetcher.owner.0 .0.clone()).expect("Should be a valid owner");
70    let repo = String::from_utf8(fetcher.repo.0 .0.clone()).expect("Should be a valid repo");
71    let tag = String::from_utf8(fetcher.tag.0 .0.clone()).expect("Should be a valid tag");
72    let binary_name =
73        String::from_utf8(binary.name.0 .0.clone()).expect("Should be a valid binary name");
74    let os_name = format!("{:?}", binary.os).to_lowercase();
75    let arch_name = format!("{:?}", binary.arch).to_lowercase();
76    // https://github.com/<owner>/<repo>/releases/download/v<tag>/<path>
77    format!("https://github.com/{owner}/{repo}/releases/download/v{tag}/{binary_name}-{os_name}-{arch_name}{ext}")
78}
79
80pub fn msg_to_error<T: Into<String>>(msg: T) -> color_eyre::Report {
81    color_eyre::Report::msg(msg.into())
82}
83
84pub fn get_service_str(svc: &NativeGithubMetadata) -> String {
85    let repo = svc.git.clone();
86    let vals: Vec<&str> = repo.split(".com/").collect();
87    vals[1].to_string()
88}
89
90pub async fn chmod_x_file<P: AsRef<Path>>(path: P) -> color_eyre::Result<()> {
91    let success = gadget_io::tokio::process::Command::new("chmod")
92        .arg("+x")
93        .arg(format!("{}", path.as_ref().display()))
94        .spawn()?
95        .wait_with_output()
96        .await?
97        .status
98        .success();
99
100    if success {
101        Ok(())
102    } else {
103        Err(color_eyre::eyre::eyre!(
104            "Failed to chmod +x {}",
105            path.as_ref().display()
106        ))
107    }
108}
109
110pub fn is_windows() -> bool {
111    std::env::consts::OS == "windows"
112}
113
114pub fn generate_running_process_status_handle(
115    process: gadget_io::tokio::process::Child,
116    service_name: &str,
117) -> (Arc<AtomicBool>, gadget_io::tokio::sync::oneshot::Sender<()>) {
118    let (stop_tx, stop_rx) = gadget_io::tokio::sync::oneshot::channel::<()>();
119    let status = Arc::new(AtomicBool::new(true));
120    let status_clone = status.clone();
121    let service_name = service_name.to_string();
122
123    let task = async move {
124        info!("Starting process execution for {service_name}");
125        let output = process.wait_with_output().await;
126        warn!("Process for {service_name} exited: {output:?}");
127        status_clone.store(false, Ordering::Relaxed);
128    };
129
130    let task = async move {
131        gadget_io::tokio::select! {
132            _ = stop_rx => {},
133            _ = task => {},
134        }
135    };
136
137    gadget_io::tokio::spawn(task);
138    (status, stop_tx)
139}
140
141pub fn bytes_to_utf8_string<T: Into<Vec<u8>>>(input: T) -> color_eyre::Result<String> {
142    String::from_utf8(input.into()).map_err(|err| msg_to_error(err.to_string()))
143}
144
145pub fn slice_32_to_sha_hex_string(hash: [u8; 32]) -> String {
146    use std::fmt::Write;
147    hash.iter().fold(String::new(), |mut acc, byte| {
148        write!(&mut acc, "{:02x}", byte).expect("Should be able to write");
149        acc
150    })
151}