Skip to content

Commit

Permalink
feat: use new custom env vars
Browse files Browse the repository at this point in the history
These two new environment variables are defined for compatibility with
cargo-dist, which uses the same variables in its installers:
axodotdev/cargo-dist#1503
  • Loading branch information
mistydemeo committed Oct 31, 2024
1 parent f9559d5 commit d6b26d5
Show file tree
Hide file tree
Showing 5 changed files with 132 additions and 20 deletions.
5 changes: 3 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions axoupdater/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ camino = { version = "1.1.6", features = ["serde1"] }
homedir = "0.3.3"
serde = "1.0.197"
tempfile = "3.10.1"
url = "2.5.2"

# axo releases
gazenot = { version = "0.3.3", features = ["client_lib"], optional = true }
Expand Down
26 changes: 26 additions & 0 deletions axoupdater/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ pub enum AxoupdateError {
#[error(transparent)]
Version(#[from] axotag::semver::Error),

/// Failed to parse a URL
#[error(transparent)]
UrlParseError(#[from] url::ParseError),

/// Failure when converting a PathBuf to a Utf8PathBuf
#[error("An internal error occurred when decoding path `{:?}' to utf8", path)]
#[diagnostic(help("This probably isn't your fault; please open an issue!"))]
Expand Down Expand Up @@ -166,4 +170,26 @@ pub enum AxoupdateError {
)]
#[diagnostic(help("This probably isn't your fault; please open an issue at https://github.com/axodotdev/axoupdater!"))]
CleanupFailed {},

/// User passed conflicting GitHub API environment variables
#[error("Both {ghe_env_var} and {github_env_var} have been set in the environment")]
#[diagnostic(help("These variables are mutually exclusive; please pick one."))]
MultipleGitHubAPIs {
/// The GitHub Enterprise env var
ghe_env_var: String,
/// The GitHub env var
github_env_var: String,
},

/// Couldn't parse the text domain (could be an IP, etc.)
#[error("Unable to parse the domain from the passed url: {url}")]
#[diagnostic(help("The {env_var} variable only takes domains. If you're using an IP, we recommend the GitHub Enterprise-style variable: {ghe_env_var}"))]
GitHubDomainParseError {
/// The GitHub env var
env_var: String,
/// The GitHub Enterprise env var
ghe_env_var: String,
/// The supplied URL
url: String,
},
}
7 changes: 6 additions & 1 deletion axoupdater/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -512,7 +512,7 @@ impl AxoUpdater {
// Also set the app-specific name for this; in the future, the
// CARGO_DIST_ version may be removed.
let app_name = self.name.clone().unwrap_or_default();
let app_name_env_var = app_name.to_ascii_uppercase().replace('-', "_");
let app_name_env_var = app_name_to_env_var(&app_name);
let app_specific_env_var = format!("{app_name_env_var}_INSTALL_DIR");
command.env(app_specific_env_var, &install_prefix);

Expand Down Expand Up @@ -621,6 +621,11 @@ fn get_app_name() -> Option<String> {
}
}

/// Returns an environment variable-compatible version of the app name.
pub fn app_name_to_env_var(app_name: &str) -> String {
app_name.to_ascii_uppercase().replace('-', "_")
}

fn root_without_bin(path: &Utf8PathBuf) -> Utf8PathBuf {
if path.file_name() == Some("bin") {
if let Some(parent) = path.parent() {
Expand Down
113 changes: 96 additions & 17 deletions axoupdater/src/release/github.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,44 @@
//! Fetching and processing from GitHub Releases

use super::{Asset, Release};
use crate::errors::*;
use crate::{app_name_to_env_var, errors::*};
use axoasset::reqwest::{
self,
header::{ACCEPT, USER_AGENT},
};
use axotag::{parse_tag, Version};
use serde::{Deserialize, Serialize};
use std::env;
use url::Url;

fn github_api(app_name: &str) -> AxoupdateResult<String> {
let formatted_app_name = app_name_to_env_var(app_name);
let ghe_env_var = format!("{}_INSTALLER_GHE_BASE_URL", formatted_app_name);
let github_env_var = format!("{}_INSTALLER_GITHUB_BASE_URL", formatted_app_name);

if env::var(&ghe_env_var).is_ok() && env::var(&github_env_var).is_ok() {
return Err(AxoupdateError::MultipleGitHubAPIs {
ghe_env_var,
github_env_var,
});
}

fn github_api() -> String {
env::var("INSTALLER_BASE_URL").unwrap_or_else(|_| "https://api.github.com".to_string())
if let Ok(value) = env::var(&ghe_env_var) {
let parsed = Url::parse(&value)?;
Ok(parsed.join("api/v3")?.to_string())
} else if let Ok(value) = env::var(&github_env_var) {
let parsed = Url::parse(&value)?;
let Some(domain) = parsed.domain() else {
return Err(AxoupdateError::GitHubDomainParseError {
env_var: github_env_var,
ghe_env_var,
url: value,
});
};
Ok(format!("{}://api.{}", parsed.scheme(), domain))
} else {
Ok("https://api.github.com".to_string())
}
}

/// A struct representing a specific GitHub Release
Expand Down Expand Up @@ -47,7 +74,7 @@ pub(crate) async fn get_latest_github_release(
token: &Option<String>,
) -> AxoupdateResult<Option<Release>> {
let client = reqwest::Client::new();
let api: String = github_api();
let api: String = github_api(app_name)?;
let mut request = client
.get(format!("{api}/repos/{owner}/{name}/releases/latest"))
.header(ACCEPT, "application/json")
Expand Down Expand Up @@ -93,7 +120,7 @@ pub(crate) async fn get_specific_github_tag(
token: &Option<String>,
) -> AxoupdateResult<Release> {
let client = reqwest::Client::new();
let api: String = github_api();
let api: String = github_api(app_name)?;
let mut request = client
.get(format!("{api}/repos/{owner}/{name}/releases/tags/{tag}"))
.header(ACCEPT, "application/json")
Expand Down Expand Up @@ -147,7 +174,7 @@ pub(crate) async fn get_github_releases(
token: &Option<String>,
) -> AxoupdateResult<Vec<Release>> {
let client = reqwest::Client::new();
let api: String = github_api();
let api: String = github_api(app_name)?;
let mut url = format!("{api}/repos/{owner}/{name}/releases");
let mut pages_remain = true;
let mut data: Vec<Release> = vec![];
Expand Down Expand Up @@ -324,37 +351,87 @@ mod test {
#[test]
#[serial] // modifying the global state environment variables
fn test_github_api_no_env_var() {
env::remove_var("INSTALLER_BASE_URL");
let result = github_api();
env::remove_var("DIST_INSTALLER_GITHUB_BASE_URL");
let result = github_api("dist").unwrap();

assert_eq!(result, "https://api.github.com");
}

#[test]
#[serial] // modifying the global state environment variables
fn test_github_api_overwrite() {
env::set_var("INSTALLER_BASE_URL", "https://magic.com");
let result = github_api();
env::set_var("DIST_INSTALLER_GITHUB_BASE_URL", "https://magic.com");
let result = github_api("dist").unwrap();
env::remove_var("DIST_INSTALLER_GITHUB_BASE_URL");

assert_eq!(result, "https://api.magic.com");
}

#[test]
#[serial] // modifying the global state environment variables
fn test_github_api_overwrite_ip() {
env::set_var("DIST_INSTALLER_GITHUB_BASE_URL", "https://127.0.0.1");
let result = github_api("dist");
env::remove_var("DIST_INSTALLER_GITHUB_BASE_URL");
assert!(result.is_err());
}

#[test]
#[serial] // modifying the global state environment variables
fn test_github_api_overwrite_bad_value() {
env::set_var("DIST_INSTALLER_GITHUB_BASE_URL", "this is not a url");
let result = github_api("dist");
env::remove_var("DIST_INSTALLER_GITHUB_BASE_URL");
assert!(result.is_err());
}

assert_eq!(result, "https://magic.com");
#[test]
#[serial] // modifying the global state environment variables
fn test_ghe_api_no_env_var() {
env::remove_var("DIST_INSTALLER_GHE_BASE_URL");
let result = github_api("dist").unwrap();

assert_eq!(result, "https://api.github.com");
}

#[test]
#[serial] // modifying the global state environment variables
fn test_ghe_api_overwrite() {
env::set_var("DIST_INSTALLER_GHE_BASE_URL", "https://magic.com");
let result = github_api("dist").unwrap();
env::remove_var("DIST_INSTALLER_GHE_BASE_URL");

assert_eq!(result, "https://magic.com/api/v3");
}

#[test]
#[serial] // modifying the global state environment variables
fn test_ghe_ip_api_overwrite() {
env::set_var("DIST_INSTALLER_GHE_BASE_URL", "https://127.0.0.1");
let result = github_api("dist").unwrap();
env::remove_var("DIST_INSTALLER_GHE_BASE_URL");

assert_eq!(result, "https://127.0.0.1/api/v3");
}

#[tokio::test]
#[serial] // modifying the global state environment variables
async fn test_get_latest_github_release_custom_endpoint() {
let server = MockServer::start_async().await;
env::set_var("INSTALLER_BASE_URL", server.base_url());
env::set_var("APP_INSTALLER_GHE_BASE_URL", server.base_url());

let latest_release_http_call = server
.mock_async(|when, then| {
when.method("GET").path("/repos/owner/name/releases/latest");
when.method("GET")
.path("/api/v3/repos/owner/name/releases/latest");
then.status(StatusCode::OK.as_u16())
.header("content-type", "application/json")
.json_body(json!(build_test_git_hub_release()));
})
.await;

let result = get_latest_github_release("name", "owner", "app", &None).await;
env::remove_var("APP_INSTALLER_GHE_BASE_URL");

assert!(result.is_ok());
assert!(result.unwrap().is_some());
Expand All @@ -380,19 +457,20 @@ mod test {
#[serial] // modifying the global state environment variables
async fn test_get_specific_github_tag_custom_endpoint() {
let server = MockServer::start_async().await;
env::set_var("INSTALLER_BASE_URL", server.base_url());
env::set_var("APP_INSTALLER_GHE_BASE_URL", server.base_url());

let release_tag_http_call = server
.mock_async(|when, then| {
when.method("GET")
.path("/repos/owner/name/releases/tags/1.0.0");
.path("/api/v3/repos/owner/name/releases/tags/1.0.0");
then.status(StatusCode::OK.as_u16())
.header("content-type", "application/json")
.json_body(json!(build_test_git_hub_release()));
})
.await;

let result = get_specific_github_tag("name", "owner", "app", "1.0.0", &None).await;
env::remove_var("APP_INSTALLER_GHE_BASE_URL");

assert!(result.is_ok());

Expand All @@ -403,18 +481,19 @@ mod test {
#[serial] // modifying the global state environment variables
async fn test_get_github_releases_custom_endpoint() {
let server = MockServer::start_async().await;
env::set_var("INSTALLER_BASE_URL", server.base_url());
env::set_var("APP_INSTALLER_GHE_BASE_URL", server.base_url());

let releases_http_call = server
.mock_async(|when, then| {
when.method("GET").path("/repos/owner/name/releases");
when.method("GET").path("/api/v3/repos/owner/name/releases");
then.status(StatusCode::OK.as_u16())
.header("content-type", "application/json")
.json_body(json!(vec![build_test_git_hub_release()]));
})
.await;

let result = get_github_releases("name", "owner", "app", &None).await;
env::remove_var("APP_INSTALLER_GHE_BASE_URL");

assert!(result.is_ok());

Expand Down

0 comments on commit d6b26d5

Please sign in to comment.