mirror of
https://github.com/cargo-bins/cargo-binstall.git
synced 2025-06-20 09:36:42 +00:00
Support private github repository (#1690)
* Refactor: Create new crate binstalk-git-repo-api Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Fix CI lint warnings Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Fix `just check`: Rm deleted features from `cargo-hack` check Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Refactor: Extract new mod error Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Ret artifact url in `has_release_artifact` So that we can use it to download from private repositories. Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Move `test_graph_ql_error_type` to mod `error` Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Fix running `cargo test` in `binstalk-git-repo-api`` Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Remove unnecessary import in mod `error::test` Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Rename mod `request`` to `release_artifacts` Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Impl draft version of fetching repo info Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Refactor: Move `HasReleaseArtifacts` failure variants into `GhApiError` Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Refactor: Use `GhRepo` in `GhRelease` Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Fix testing Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Refactor: Return `'static` future Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Refactor: Make sure `'static` Future is returned To make it easier to create generic function Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Add logging to unit testing Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Fix unit testing Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Refactor: Extract new fn `GhApiClient::do_fetch` Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Rm unused `percent_encode_http_url_path` Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Fix `cargo test` run on CI `cargo test` run all tests in one process. As such, `set_global_default` would fail on the second call. Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Optimize `GhApiClient::do_fetch`: Avoid unnecessary restful API call Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Refactor: Rm param `auth_token` for restful API fn which is always set to `None` Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Impl new API `GhApiClient::get_repo_info` Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Fix unit test for `GhApiClient::get_repo_info` Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Refactor testing: Parameter-ize testing Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Parallelise `test_get_repo_info` Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Refactor: Create parameter-ised `test_has_release_artifact` Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Parallelize `test_has_release_artifact` Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Refactor: `gh_api_client::test::create_client` shall not be `async` as there is no `.await` in it. Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Return `Url` in `GhApiClient::has_release_artifact` Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Impl new API `GhApiClient::download_artifact` Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Remove unused deps added to binstalk-git-repo-api Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Fix clippy lints Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Add new API `GhApiClient::remote_client` Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Add `GhApiClient::has_gh_token` Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Add `GhRepo::try_extract_from_url` Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Rename `ReleaseArtifactUrl` to `GhReleaseArtifactUrl` Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Add new fn `Download::with_data_verifier` Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * feature: Support private repository Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Fix clippy lints Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Add e2e-test/private-github-repo Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Fix clippy lints Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Fix `launch_baseline_find_tasks`: Retry on rate limit Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Fix test failure: Retry on rate limit Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Temporarily enable debug output for e2e-test-private-github-repo Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Fix `get_repo_info`: Retry on rate limit Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Improve `debug!` logging Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Add more debug logging Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Add more debugging Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Add more debug logging Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Apply suggestions from code review * Fix compilation Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Fix cargo fmt Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com> * Add crate binstalk-git-repo-api to release-pr.yml * Update crates/binstalk-git-repo-api/Cargo.toml * Apply suggestions from code review * Update crates/binstalk/Cargo.toml --------- Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com>
This commit is contained in:
parent
48ee0b0e3e
commit
1dbd2460a3
30 changed files with 1838 additions and 1127 deletions
|
@ -0,0 +1,187 @@
|
|||
use std::{
|
||||
borrow::Borrow,
|
||||
collections::HashSet,
|
||||
fmt,
|
||||
future::Future,
|
||||
hash::{Hash, Hasher},
|
||||
};
|
||||
|
||||
use binstalk_downloader::remote::{self};
|
||||
use compact_str::{CompactString, ToCompactString};
|
||||
use serde::Deserialize;
|
||||
use url::Url;
|
||||
|
||||
use super::{
|
||||
common::{issue_graphql_query, issue_restful_api},
|
||||
GhApiError, GhRelease, GhRepo,
|
||||
};
|
||||
|
||||
// Only include fields we do care about
|
||||
|
||||
#[derive(Eq, Deserialize, Debug)]
|
||||
struct Artifact {
|
||||
name: CompactString,
|
||||
url: Url,
|
||||
}
|
||||
|
||||
// Manually implement PartialEq and Hash to ensure it will always produce the
|
||||
// same hash as a str with the same content, and that the comparison will be
|
||||
// the same to coparing a string.
|
||||
|
||||
impl PartialEq for Artifact {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.name.eq(&other.name)
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for Artifact {
|
||||
fn hash<H>(&self, state: &mut H)
|
||||
where
|
||||
H: Hasher,
|
||||
{
|
||||
let s: &str = self.name.as_str();
|
||||
s.hash(state)
|
||||
}
|
||||
}
|
||||
|
||||
// Implement Borrow so that we can use call
|
||||
// `HashSet::contains::<str>`
|
||||
|
||||
impl Borrow<str> for Artifact {
|
||||
fn borrow(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize)]
|
||||
pub(super) struct Artifacts {
|
||||
assets: HashSet<Artifact>,
|
||||
}
|
||||
|
||||
impl Artifacts {
|
||||
/// get url for downloading the artifact using GitHub API (for private repository).
|
||||
pub(super) fn get_artifact_url(&self, artifact_name: &str) -> Option<Url> {
|
||||
self.assets
|
||||
.get(artifact_name)
|
||||
.map(|artifact| artifact.url.clone())
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn fetch_release_artifacts_restful_api(
|
||||
client: &remote::Client,
|
||||
GhRelease {
|
||||
repo: GhRepo { owner, repo },
|
||||
tag,
|
||||
}: &GhRelease,
|
||||
) -> impl Future<Output = Result<Artifacts, GhApiError>> + Send + Sync + 'static {
|
||||
issue_restful_api(client, &["repos", owner, repo, "releases", "tags", tag])
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct GraphQLData {
|
||||
repository: Option<GraphQLRepo>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct GraphQLRepo {
|
||||
release: Option<GraphQLRelease>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct GraphQLRelease {
|
||||
#[serde(rename = "releaseAssets")]
|
||||
assets: GraphQLReleaseAssets,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct GraphQLReleaseAssets {
|
||||
nodes: Vec<Artifact>,
|
||||
#[serde(rename = "pageInfo")]
|
||||
page_info: GraphQLPageInfo,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct GraphQLPageInfo {
|
||||
#[serde(rename = "endCursor")]
|
||||
end_cursor: Option<CompactString>,
|
||||
#[serde(rename = "hasNextPage")]
|
||||
has_next_page: bool,
|
||||
}
|
||||
|
||||
enum FilterCondition {
|
||||
Init,
|
||||
After(CompactString),
|
||||
}
|
||||
|
||||
impl fmt::Display for FilterCondition {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
// GitHub imposes a limit of 100 for the value passed to param "first"
|
||||
FilterCondition::Init => f.write_str("first:100"),
|
||||
FilterCondition::After(end_cursor) => write!(f, r#"first:100,after:"{end_cursor}""#),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn fetch_release_artifacts_graphql_api(
|
||||
client: &remote::Client,
|
||||
GhRelease {
|
||||
repo: GhRepo { owner, repo },
|
||||
tag,
|
||||
}: &GhRelease,
|
||||
auth_token: &str,
|
||||
) -> impl Future<Output = Result<Artifacts, GhApiError>> + Send + Sync + 'static {
|
||||
let client = client.clone();
|
||||
let auth_token = auth_token.to_compact_string();
|
||||
|
||||
let base_query_prefix = format!(
|
||||
r#"
|
||||
query {{
|
||||
repository(owner:"{owner}",name:"{repo}") {{
|
||||
release(tagName:"{tag}") {{"#
|
||||
);
|
||||
|
||||
let base_query_suffix = r#"
|
||||
nodes { name url }
|
||||
pageInfo { endCursor hasNextPage }
|
||||
}}}}"#
|
||||
.trim();
|
||||
|
||||
async move {
|
||||
let mut artifacts = Artifacts::default();
|
||||
let mut cond = FilterCondition::Init;
|
||||
let base_query_prefix = base_query_prefix.trim();
|
||||
|
||||
loop {
|
||||
let query = format!(
|
||||
r#"
|
||||
{base_query_prefix}
|
||||
releaseAssets({cond}) {{
|
||||
{base_query_suffix}"#
|
||||
);
|
||||
|
||||
let data: GraphQLData = issue_graphql_query(&client, query, &auth_token).await?;
|
||||
|
||||
let assets = data
|
||||
.repository
|
||||
.and_then(|repository| repository.release)
|
||||
.map(|release| release.assets);
|
||||
|
||||
if let Some(assets) = assets {
|
||||
artifacts.assets.extend(assets.nodes);
|
||||
|
||||
match assets.page_info {
|
||||
GraphQLPageInfo {
|
||||
end_cursor: Some(end_cursor),
|
||||
has_next_page: true,
|
||||
} => {
|
||||
cond = FilterCondition::After(end_cursor);
|
||||
}
|
||||
_ => break Ok(artifacts),
|
||||
}
|
||||
} else {
|
||||
break Err(GhApiError::NotFound);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue