Skip to content

Commit

Permalink
Add a new --api-url-base CLI arg and change UbiBuilder::url_base
Browse files Browse the repository at this point in the history
…to `api_base_url`
  • Loading branch information
autarch committed Dec 26, 2024
1 parent 98ea3b6 commit aa7ecd5
Show file tree
Hide file tree
Showing 7 changed files with 111 additions and 49 deletions.
3 changes: 3 additions & 0 deletions Changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
with a new `UbiBuilder::forge` method.
- When looking for macOS assets, `ubi` will now match against `macosx` in asset names, not just
`macos` and `osx`. Implemented by @kattouf (Vasiliy Kattouf). GH #80.
- Added a new `--api-url-base` CLI argument. Requested by Olaf Alders. GH #69.
- Renamed the `UbiBuilder::url_base` method to `api_base_url` and changed it to take a `&str`
instead of a `String`, which is consistent with all the other builder methods.

## 0.2.4 - 2024-11-24

Expand Down
68 changes: 40 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,34 +78,41 @@ current directory.
Usage: ubi [OPTIONS]
Options:
-p, --project <project> The project you want to install, like houseabsolute/precious or
https://github.com/houseabsolute/precious.
-t, --tag <tag> The tag to download. Defaults to the latest release.
-u, --url <url> The url of the file to download. This can be provided instead of a
project or tag. This will not use the forge site's API, so you will
never hit its API limits. With this parameter, you do not need to set a
token env var except for private repos.
--self-upgrade Use ubi to upgrade to the latest version of ubi. The --exe, --in,
--project, --tag, and --url args will be ignored.
-i, --in <in> The directory in which the binary should be placed. Defaults to ./bin.
-e, --exe <exe> The name of this project's executable. By default this is the same as
the project name, so for houseabsolute/precious we look for precious or
precious.exe. When running on Windows the ".exe" suffix will be added
as needed.
-m, --matching <matching> A string that will be matched against the release filename when there
are multiple matching files for your OS/arch. For example, there may be
multiple releases for an OS/arch that differ by compiler (MSVC vs. gcc)
or linked libc (glibc vs. musl). Note that this will be ignored if
there is only one matching release filename for your OS/arch.
--forge <forge> The forge to use. If this isn't set, then the value of --project or
--url will be checked for gitlab.com. If this contains any other domain
_or_ if it does not have a domain at all, then the default is GitHub.
[possible values: github, gitlab]
-v, --verbose Enable verbose output.
-d, --debug Enable debugging output.
-q, --quiet Suppresses most output.
-h, --help Print help
-V, --version Print version
-p, --project <project> The project you want to install, like houseabsolute/precious or
https://github.com/houseabsolute/precious.
-t, --tag <tag> The tag to download. Defaults to the latest release.
-u, --url <url> The url of the file to download. This can be provided instead
of a project or tag. This will not use the forge site's API, so
you will never hit its API limits. With this parameter, you do
not need to set a token env var except for private repos.
--self-upgrade Use ubi to upgrade to the latest version of ubi. The --exe,
--in, --project, --tag, and --url args will be ignored.
-i, --in <in> The directory in which the binary should be placed. Defaults to
./bin.
-e, --exe <exe> The name of this project's executable. By default this is the
same as the project name, so for houseabsolute/precious we look
for precious or precious.exe. When running on Windows the
".exe" suffix will be added as needed.
-m, --matching <matching> A string that will be matched against the release filename when
there are multiple matching files for your OS/arch. For
example, there may be multiple releases for an OS/arch that
differ by compiler (MSVC vs. gcc) or linked libc (glibc vs.
musl). Note that this will be ignored if there is only one
matching release filename for your OS/arch.
--forge <forge> The forge to use. If this isn't set, then the value of
--project or --url will be checked for gitlab.com. If this
contains any other domain _or_ if it does not have a domain at
all, then the default is GitHub. [possible values: github,
gitlab]
--api-base-url <api-base-url> The the base URL for the forge site's API. This is useful for
testing or if you want to operate against an Enterprise version
of GitHub or GitLab. This should be something like
`https://github.my-corp.example.com/api/v4`.
-v, --verbose Enable verbose output.
-d, --debug Enable debugging output.
-q, --quiet Suppresses most output.
-h, --help Print help
-V, --version Print version
```

## Using a Forge Token
Expand Down Expand Up @@ -244,6 +251,11 @@ parameter, then you should use the `--tag` parameter to specify the released ver
install. Otherwise `ubi` will always download the latest version, which can lead to surprises,
especially if you are running the tools you download in CI.

## Using `ubi` with GitHub Enterprise or GitLab for Enterprise

The command line tool takes an `--api-base-url` flag for this purpose. This should be the full URL
to the root of the API, something like `https://github.my-corp.example.com/api/v4`.

## Why This Is Useful

With the rise of Go and Rust, it has become increasingly common for very useful tools like
Expand Down
8 changes: 8 additions & 0 deletions ubi-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,11 @@ fn cmd() -> Command {
" does not have a domain at all, then the default is GitHub.",
)),
)
.arg(Arg::new("api-base-url").long("api-base-url").help(concat!(
"The the base URL for the forge site's API. This is useful for testing or if",
" you want to operate against an Enterprise version of GitHub or GitLab. This",
" should be something like `https://github.my-corp.example.com/api/v4`.",
)))
.arg(
Arg::new("verbose")
.short('v')
Expand Down Expand Up @@ -198,6 +203,9 @@ fn make_ubi<'a>(
if let Some(ft) = matches.get_one::<String>("forge") {
builder = builder.forge(ForgeType::from_str(ft)?);
}
if let Some(url) = matches.get_one::<String>("api-base-url") {
builder = builder.api_base_url(url);
}

Ok((builder.build()?, None))
}
Expand Down
13 changes: 7 additions & 6 deletions ubi/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ pub struct UbiBuilder<'a> {
gitlab_token: Option<&'a str>,
platform: Option<&'a Platform>,
is_musl: Option<bool>,
url_base: Option<String>,
api_base_url: Option<&'a str>,
forge: Option<ForgeType>,
}

Expand Down Expand Up @@ -146,11 +146,12 @@ impl<'a> UbiBuilder<'a> {
self
}

/// Set the base URL for the forge site's API. This is useful for testing or if you want to operate
/// against an Enterprise version of GitHub or GitLab.
/// Set the base URL for the forge site's API. This is useful for testing or if you want to
/// operate against an Enterprise version of GitHub or GitLab. This should be something like
/// `https://github.my-corp.example.com/api/v4`.
#[must_use]
pub fn url_base(mut self, url_base: String) -> Self {
self.url_base = Some(url_base);
pub fn api_base_url(mut self, api_base_url: &'a str) -> Self {
self.api_base_url = Some(api_base_url);
self
}

Expand Down Expand Up @@ -193,7 +194,7 @@ impl<'a> UbiBuilder<'a> {
}

fn new_forge(&self, project_name: String, forge_type: &ForgeType) -> Result<Box<dyn Forge>> {
let api_base = self.url_base.as_deref().map(Url::parse).transpose()?;
let api_base = self.api_base_url.map(Url::parse).transpose()?;
Ok(match forge_type {
ForgeType::GitHub => Box::new(GitHub::new(
project_name,
Expand Down
21 changes: 18 additions & 3 deletions ubi/src/github.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use url::Url;
pub(crate) struct GitHub {
project_name: String,
tag: Option<String>,
api_base: Url,
api_base_url: Url,
token: Option<String>,
}

Expand All @@ -42,7 +42,7 @@ impl Forge for GitHub {
let owner = parts.next().unwrap();
let repo = parts.next().unwrap();

let mut url = self.api_base.clone();
let mut url = self.api_base_url.clone();
url.path_segments_mut()
.expect("could not get path segments for url")
.push("repos")
Expand Down Expand Up @@ -90,7 +90,7 @@ impl GitHub {
Self {
project_name,
tag,
api_base: api_base.unwrap_or_else(|| ForgeType::GitHub.api_base()),
api_base_url: api_base.unwrap_or_else(|| ForgeType::GitHub.api_base()),
token,
}
}
Expand Down Expand Up @@ -171,4 +171,19 @@ mod tests {

Ok(())
}

#[test]
fn api_base_url() {
let github = GitHub::new(
"houseabsolute/ubi".to_string(),
None,
Some(Url::parse("https://github.example.com/api/v4").unwrap()),
None,
);
let url = github.release_info_url();
assert_eq!(
url.as_str(),
"https://github.example.com/api/v4/repos/houseabsolute/ubi/releases/latest"
);
}
}
21 changes: 18 additions & 3 deletions ubi/src/gitlab.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use url::Url;
pub(crate) struct GitLab {
project_name: String,
tag: Option<String>,
api_base: Url,
api_base_url: Url,
token: Option<String>,
}

Expand All @@ -41,7 +41,7 @@ impl Forge for GitLab {
}

fn release_info_url(&self) -> Url {
let mut url = self.api_base.clone();
let mut url = self.api_base_url.clone();
url.path_segments_mut()
.expect("could not get path segments for url")
.push("projects")
Expand Down Expand Up @@ -92,7 +92,7 @@ impl GitLab {
Self {
project_name,
tag,
api_base: api_base.unwrap_or_else(|| ForgeType::GitLab.api_base()),
api_base_url: api_base.unwrap_or_else(|| ForgeType::GitLab.api_base()),
token,
}
}
Expand Down Expand Up @@ -176,4 +176,19 @@ mod tests {

Ok(())
}

#[test]
fn api_base_url() {
let gitlab = GitLab::new(
"houseabsolute/ubi".to_string(),
None,
Some(Url::parse("https://gitlab.example.com/api/v4").unwrap()),
None,
);
let url = gitlab.release_info_url();
assert_eq!(
url.as_str(),
"https://gitlab.example.com/api/v4/projects/houseabsolute%2Fubi/releases/permalink/latest"
);
}
}
26 changes: 17 additions & 9 deletions ubi/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ async fn asset_picking() -> Result<()> {
];

let mut server = Server::new_async().await;
let url = server.url();
let m1 = server
.mock("GET", "/repos/houseabsolute/ubi/releases/latest")
.match_header(ACCEPT.as_str(), "application/json")
Expand Down Expand Up @@ -173,7 +174,7 @@ async fn asset_picking() -> Result<()> {
.project("houseabsolute/ubi")
.platform(platform)
.is_musl(false)
.url_base(server.url())
.api_base_url(&url)
.build()?;
let asset = ubi.asset().await?;
let expect_ubi_url = Url::parse(&format!(
Expand All @@ -196,7 +197,7 @@ async fn asset_picking() -> Result<()> {
.project("houseabsolute/omegasort")
.platform(platform)
.is_musl(false)
.url_base(server.url())
.api_base_url(&url)
.build()?;
let asset = ubi.asset().await?;
let expect_omegasort_url = Url::parse(&format!(
Expand Down Expand Up @@ -510,6 +511,7 @@ async fn matching_unusual_names() -> Result<()> {
];

let mut server = Server::new_async().await;
let url = server.url();
let m1 = server
.mock("GET", "/repos/protocolbuffers/protobuf/releases/latest")
.match_header(ACCEPT.as_str(), "application/json")
Expand All @@ -527,7 +529,7 @@ async fn matching_unusual_names() -> Result<()> {
let mut ubi = UbiBuilder::new()
.project("protocolbuffers/protobuf")
.platform(platform)
.url_base(server.url())
.api_base_url(&url)
.build()?;
let asset = ubi.asset().await?;
assert_eq!(
Expand Down Expand Up @@ -633,6 +635,7 @@ async fn mkcert_matching() -> Result<()> {
];

let mut server = Server::new_async().await;
let url = server.url();
let m1 = server
.mock("GET", "/repos/FiloSottile/mkcert/releases/latest")
.match_header(ACCEPT.as_str(), "application/json")
Expand All @@ -650,7 +653,7 @@ async fn mkcert_matching() -> Result<()> {
let mut ubi = UbiBuilder::new()
.project("FiloSottile/mkcert")
.platform(platform)
.url_base(server.url())
.api_base_url(&url)
.build()?;
let asset = ubi.asset().await?;
assert_eq!(
Expand Down Expand Up @@ -727,6 +730,7 @@ async fn jq_matching() -> Result<()> {
];

let mut server = Server::new_async().await;
let url = server.url();
let m1 = server
.mock("GET", "/repos/stedolan/jq/releases/latest")
.match_header(ACCEPT.as_str(), "application/json")
Expand All @@ -744,7 +748,7 @@ async fn jq_matching() -> Result<()> {
let mut ubi = UbiBuilder::new()
.project("stedolan/jq")
.platform(platform)
.url_base(server.url())
.api_base_url(&url)
.build()?;
let asset = ubi.asset().await?;
assert_eq!(
Expand Down Expand Up @@ -799,6 +803,7 @@ async fn multiple_matches() -> Result<()> {
let platforms = ["x86_64-pc-windows-gnu", "i686-pc-windows-gnu"];

let mut server = Server::new_async().await;
let url = server.url();
let m1 = server
.mock("GET", "/repos/test/multiple-matches/releases/latest")
.match_header(ACCEPT.as_str(), "application/json")
Expand All @@ -815,7 +820,7 @@ async fn multiple_matches() -> Result<()> {
let mut ubi = UbiBuilder::new()
.project("test/multiple-matches")
.platform(platform)
.url_base(server.url())
.api_base_url(&url)
.build()?;
let asset = ubi.asset().await?;
let expect = "mm-i686-pc-windows-gnu.zip";
Expand Down Expand Up @@ -844,6 +849,7 @@ const MULTIPLE_MATCHES_RESPONSE: &str = r#"
#[test(tokio::test)]
async fn macos_arm() -> Result<()> {
let mut server = Server::new_async().await;
let url = server.url();
let m1 = server
.mock("GET", "/repos/test/macos/releases/latest")
.match_header(ACCEPT.as_str(), "application/json")
Expand All @@ -860,7 +866,7 @@ async fn macos_arm() -> Result<()> {
let mut ubi = UbiBuilder::new()
.project("test/macos")
.platform(platform)
.url_base(server.url())
.api_base_url(&url)
.build()?;

{
Expand Down Expand Up @@ -933,6 +939,7 @@ const MACOS_RESPONSE2: &str = r#"
async fn os_without_arch() -> Result<()> {
{
let mut server = Server::new_async().await;
let url = server.url();
let m1 = server
.mock("GET", "/repos/test/os-without-arch/releases/latest")
.match_header(ACCEPT.as_str(), "application/json")
Expand All @@ -949,7 +956,7 @@ async fn os_without_arch() -> Result<()> {
let mut ubi = UbiBuilder::new()
.project("test/os-without-arch")
.platform(platform)
.url_base(server.url())
.api_base_url(&url)
.build()?;
let asset = ubi.asset().await?;
let expect = "gvproxy-darwin";
Expand All @@ -960,6 +967,7 @@ async fn os_without_arch() -> Result<()> {

{
let mut server = Server::new_async().await;
let url = server.url();
let m1 = server
.mock("GET", "/repos/test/os-without-arch/releases/latest")
.match_header(ACCEPT.as_str(), "application/json")
Expand All @@ -976,7 +984,7 @@ async fn os_without_arch() -> Result<()> {
let mut ubi = UbiBuilder::new()
.project("test/os-without-arch")
.platform(platform)
.url_base(server.url())
.api_base_url(&url)
.build()?;
let asset = ubi.asset().await;
assert!(
Expand Down

0 comments on commit aa7ecd5

Please sign in to comment.