Skip to content

Commit

Permalink
Add multi-site support to ubi crate
Browse files Browse the repository at this point in the history
---

For more details, open the [Copilot Workspace session](https://copilot-workspace.githubnext.com/houseabsolute/ubi?shareId=XXXX-XXXX-XXXX-XXXX).
  • Loading branch information
autarch committed Dec 23, 2024
1 parent ce9785f commit 1fe18c4
Show file tree
Hide file tree
Showing 5 changed files with 123 additions and 10 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,4 @@ zip = { version = "2.2.1", default-features = false, features = [
"lzma",
"zstd",
] }
async-trait = "0.1.50"
13 changes: 13 additions & 0 deletions ubi/src/forge.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
use anyhow::Result;

Check failure on line 1 in ubi/src/forge.rs

View workflow job for this annotation

GitHub Actions / Check that code is lint clean using precious

Linting with rustfmt failed

Check failure on line 1 in ubi/src/forge.rs

View workflow job for this annotation

GitHub Actions / Check that code is lint clean using precious

Linting with rustfmt failed
use async_trait::async_trait;
use reqwest::Client;
use url::Url;

use crate::release::{Asset, Release};

#[async_trait]
pub(crate) trait Forge {
fn new(project_name: String, tag: Option<String>, url: Option<Url>, api_base: Option<String>) -> Self;
async fn fetch_assets(&self, client: &Client) -> Result<Vec<Asset>>;
async fn release_info(&self, client: &Client) -> Result<Release>;
}
9 changes: 6 additions & 3 deletions ubi/src/fetcher.rs → ubi/src/github.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use crate::forge::Forge;
use crate::release::{Asset, Release};
use anyhow::Result;
use async_trait::async_trait;
use reqwest::{header::HeaderValue, header::ACCEPT, Client};
use url::Url;

Expand All @@ -13,8 +15,9 @@ pub(crate) struct GitHubAssetFetcher {

const GITHUB_API_BASE: &str = "https://api.github.com";

impl GitHubAssetFetcher {
pub(crate) fn new(
#[async_trait]
impl Forge for GitHubAssetFetcher {
fn new(
project_name: String,
tag: Option<String>,
url: Option<Url>,
Expand All @@ -28,7 +31,7 @@ impl GitHubAssetFetcher {
}
}

pub(crate) async fn fetch_assets(&self, client: &Client) -> Result<Vec<Asset>> {
async fn fetch_assets(&self, client: &Client) -> Result<Vec<Asset>> {
if let Some(url) = &self.url {
return Ok(vec![Asset {
name: url.path().split('/').last().unwrap().to_string(),
Expand Down
75 changes: 75 additions & 0 deletions ubi/src/gitlab.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
use crate::forge::Forge;
use crate::release::{Asset, Release};
use anyhow::Result;
use async_trait::async_trait;
use reqwest::{header::HeaderValue, header::ACCEPT, Client};
use url::Url;

#[derive(Debug)]
pub(crate) struct GitLabAssetFetcher {
project_name: String,
tag: Option<String>,
url: Option<Url>,
gitlab_api_base: String,
}

const GITLAB_API_BASE: &str = "https://gitlab.com/api/v4";

#[async_trait]
impl Forge for GitLabAssetFetcher {
fn new(
project_name: String,
tag: Option<String>,
url: Option<Url>,
gitlab_api_base: Option<String>,
) -> Self {
Self {
project_name,
tag,
url,
gitlab_api_base: gitlab_api_base.unwrap_or(GITLAB_API_BASE.to_string()),
}
}

async fn fetch_assets(&self, client: &Client) -> Result<Vec<Asset>> {
if let Some(url) = &self.url {
return Ok(vec![Asset {
name: url.path().split('/').last().unwrap().to_string(),
url: url.clone(),
}]);
}

Ok(self.release_info(client).await?.assets)
}

async fn release_info(&self, client: &Client) -> Result<Release> {
let mut parts = self.project_name.split('/');
let owner = parts.next().unwrap();
let repo = parts.next().unwrap();

let url = match &self.tag {
Some(tag) => format!(
"{}/projects/{}/releases/{}",
self.gitlab_api_base,
urlencoding::encode(&format!("{}/{}", owner, repo)),
tag,
),
None => format!(
"{}/projects/{}/releases",
self.gitlab_api_base,
urlencoding::encode(&format!("{}/{}", owner, repo)),
),
};
let req = client
.get(url)
.header(ACCEPT, HeaderValue::from_str("application/json")?)
.build()?;
let resp = client.execute(req).await?;

if let Err(e) = resp.error_for_status_ref() {
return Err(anyhow::Error::new(e));
}

Ok(resp.json::<Release>().await?)
}
}
35 changes: 28 additions & 7 deletions ubi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,17 @@

mod arch;
mod extension;
mod fetcher;
mod installer;
mod os;
mod picker;
mod release;
mod forge;
mod github;
mod gitlab;

use crate::{
fetcher::GitHubAssetFetcher, installer::Installer, picker::AssetPicker, release::Asset,
release::Download,
installer::Installer, picker::AssetPicker, release::Asset,
release::Download, forge::Forge, github::GitHubAssetFetcher, gitlab::GitLabAssetFetcher,
};
use anyhow::{anyhow, Result};
use log::debug;
Expand Down Expand Up @@ -74,6 +76,7 @@ pub struct UbiBuilder<'a> {
platform: Option<&'a Platform>,
is_musl: Option<bool>,
github_api_url_base: Option<String>,
forge: Option<ForgeType>,
}

impl<'a> UbiBuilder<'a> {
Expand Down Expand Up @@ -174,6 +177,13 @@ impl<'a> UbiBuilder<'a> {
self
}

/// Set the forge type to use for fetching assets and release information.
#[must_use]
pub fn forge(mut self, forge: ForgeType) -> Self {
self.forge = Some(forge);
self
}

const TARGET: &'static str = env!("TARGET");

/// Builds a new [`Ubi`] instance and returns it.
Expand Down Expand Up @@ -225,6 +235,7 @@ impl<'a> UbiBuilder<'a> {
None => platform_is_musl(platform),
},
self.github_api_url_base,
self.forge,
)
}
}
Expand All @@ -251,7 +262,7 @@ fn platform_is_musl(platform: &Platform) -> bool {
/// [`UbiBuilder`] struct to create a new `Ubi` instance.
#[derive(Debug)]
pub struct Ubi<'a> {
asset_fetcher: GitHubAssetFetcher,
asset_fetcher: Box<dyn Forge>,
asset_picker: AssetPicker<'a>,
installer: Installer,
reqwest_client: Client,
Expand All @@ -271,6 +282,7 @@ impl<'a> Ubi<'a> {
platform: &'a Platform,
is_musl: bool,
github_api_url_base: Option<String>,
forge: Option<ForgeType>,
) -> Result<Ubi<'a>> {
let url = if let Some(u) = url {
Some(Url::parse(u)?)
Expand All @@ -280,13 +292,22 @@ impl<'a> Ubi<'a> {
let project_name = Self::parse_project_name(project, url.as_ref())?;
let exe = Self::exe_name(exe, &project_name, platform);
let install_path = Self::install_path(install_dir, &exe)?;
Ok(Ubi {
asset_fetcher: GitHubAssetFetcher::new(
let asset_fetcher: Box<dyn Forge> = match forge {
Some(ForgeType::GitLab) => Box::new(GitLabAssetFetcher::new(
project_name,
tag.map(std::string::ToString::to_string),
url,
github_api_url_base,
),
)),
_ => Box::new(GitHubAssetFetcher::new(
project_name,
tag.map(std::string::ToString::to_string),
url,
github_api_url_base,
)),
};
Ok(Ubi {
asset_fetcher,
asset_picker: AssetPicker::new(matching, platform, is_musl),
installer: Installer::new(install_path, exe),
reqwest_client: Self::reqwest_client(github_token)?,
Expand Down

0 comments on commit 1fe18c4

Please sign in to comment.