Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

An auto-discovery implementation #5

Merged
merged 3 commits into from
Oct 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -166,12 +166,18 @@ run_test TARGET:

# Integration Testing (requires Nix)
itest:
set -euxo pipefail
# TODO: self hosted gitlab

just run_test "http://localhost:3000/v1/codeberg/cafkafk/hello.tar.gz"
just run_test "http://localhost:3000/v1/github/cafkafk/hello.tar.gz"
just run_test "http://localhost:3000/v1/gitlab/gitlab.com/cafkafk/hello.tar.gz"
just run_test "http://localhost:3000/v1/gitlab/gitlab.com/cafkafk/hello.tar.gz"
just run_test "http://localhost:3000/v1/forgejo/next.forgejo.org/cafkafk/hello.tar.gz"
just run_test "http://localhost:3000/v1/flakehub/cafkafk/hello/v/v0.0.1.tar.gz"
# TODO: self hosted gitlab

just run_test "http://localhost:3000/v1/codeberg.org/cafkafk/hello.tar.gz"
just run_test "http://localhost:3000/v1/github.com/cafkafk/hello.tar.gz"
just run_test "http://localhost:3000/v1/gitlab.com/cafkafk/hello.tar.gz"
just run_test "http://localhost:3000/v1/next.forgejo.org/cafkafk/hello.tar.gz"
just run_test "http://localhost:3000/v1/flakehub.com/cafkafk/hello/v/v0.0.1.tar.gz"

@echo "tests passsed :3"
72 changes: 72 additions & 0 deletions src/api/v1/auto_discovery/endpoints/auto_discover.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// SPDX-FileCopyrightText: 2023 Gergely Nagy
// SPDX-FileContributor: Gergely Nagy
//
// SPDX-License-Identifier: AGPL-3.0-only

use axum::{
extract::Path,
http::StatusCode,
response::{IntoResponse, Redirect},
};

use crate::api::v1::{forgejo::is_forgejo, gitlab::is_gitlab};

#[allow(unused)]
use log::{debug, error, info, trace, warn};

#[derive(Debug)]
struct AutoDiscovery(String, Option<String>);

impl AutoDiscovery {
pub fn new_for(host: &str) -> Self {
Self(host.to_string(), None)
}

pub async fn try_forgejo(self) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
if self.1.is_some() {
return Ok(self);
}
if is_forgejo(&self.0).await? {
Ok(Self(
self.0.clone(),
Some(format!("/v1/forgejo/{}", self.0)),
))
} else {
Ok(Self(self.0, None))
}
}

pub async fn try_gitlab(self) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
if self.1.is_some() {
return Ok(self);
}
if is_gitlab(&self.0).await? {
Ok(Self(self.0.clone(), Some(format!("/v1/gitlab/{}", self.0))))
} else {
Ok(Self(self.0, None))
}
}

pub fn url(self, url: &str) -> Option<String> {
self.1.map(|forge_api| format!("{}/{}", forge_api, url))
}
}

pub async fn auto_discover(Path((host, url)): Path<(String, String)>) -> impl IntoResponse {
let target = AutoDiscovery::new_for(&host)
.try_forgejo()
.await
.expect("failed to await try_forgejo")
.try_gitlab()
.await
.expect("failed to await try_gitlab")
.url(&url);
trace!("target: {target:#?}");

if let Some(target_url) = target {
Redirect::to(&target_url).into_response()
} else {
let body = format!("Unable to auto-discover the forge at {:#?} :(", host);
(StatusCode::BAD_REQUEST, body).into_response()
}
}
7 changes: 7 additions & 0 deletions src/api/v1/auto_discovery/endpoints/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// SPDX-FileCopyrightText: 2023 Gergely Nagy
// SPDX-FileContributor: Gergely Nagy
//
// SPDX-License-Identifier: AGPL-3.0-only

mod auto_discover;
pub use self::auto_discover::auto_discover;
8 changes: 8 additions & 0 deletions src/api/v1/auto_discovery/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// SPDX-FileCopyrightText: 2023 Gergely Nagy
// SPDX-FileContributor: Gergely Nagy
//
// SPDX-License-Identifier: AGPL-3.0-only

pub mod routes;

mod endpoints;
12 changes: 12 additions & 0 deletions src/api/v1/auto_discovery/routes.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// SPDX-FileCopyrightText: 2023 Gergely Nagy
// SPDX-FileContributor: Gergely Nagy
//
// SPDX-License-Identifier: AGPL-3.0-only

use super::endpoints::auto_discover;

use axum::{routing::get, Router};

pub fn get_routes() -> Router {
Router::new().route("/:host/*req", get(auto_discover))
}
1 change: 1 addition & 0 deletions src/api/v1/forgejo/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ pub mod routes;

mod endpoints;
mod utils;
pub use utils::is_forgejo;
26 changes: 26 additions & 0 deletions src/api/v1/forgejo/utils/is_forgejo.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// SPDX-FileCopyrightText: 2023 Gergely Nagy
// SPDX-FileContributor: Gergely Nagy
//
// SPDX-License-Identifier: AGPL-3.0-only

#[allow(unused)]
use log::{debug, error, info, trace, warn};

use reqwest::{
header::{ACCEPT, USER_AGENT},
Url,
};

pub async fn is_forgejo(host: &str) -> Result<bool, Box<dyn std::error::Error + Send + Sync>> {
// I couldn't find a more reasonable way to detect Forgejo, so we'll check
// an API endpoint that is specific to this forge, and if it exists, we
// assume it's a Forgejo instance.
let uri = Url::parse(&format!("https://{}/api/v1/settings/api", &host))?;
let client = reqwest::Client::builder().user_agent(USER_AGENT).build()?;
let res = client
.get(uri)
.header(ACCEPT, "application/json")
.send()
.await?;
Ok(res.status() == 200)
}
2 changes: 2 additions & 0 deletions src/api/v1/forgejo/utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@

mod forgejo_api_get_latest_tag_url;
pub use self::forgejo_api_get_latest_tag_url::forgejo_api_get_latest_tag_url;
mod is_forgejo;
pub use self::is_forgejo::is_forgejo;
1 change: 1 addition & 0 deletions src/api/v1/gitlab/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ pub mod routes;

mod endpoints;
mod utils;
pub use utils::is_gitlab;
29 changes: 29 additions & 0 deletions src/api/v1/gitlab/utils/is_gitlab.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// SPDX-FileCopyrightText: 2023 Gergely Nagy
// SPDX-FileContributor: Gergely Nagy
//
// SPDX-License-Identifier: AGPL-3.0-only

#[allow(unused)]
use log::{debug, error, info, trace, warn};

use reqwest::{
header::{ACCEPT, USER_AGENT},
Url,
};

pub async fn is_gitlab(host: &str) -> Result<bool, Box<dyn std::error::Error + Send + Sync>> {
// Detecting GitLab is a bit tricky: while it has a /version and a /metadata
// endpoint, both require authentication, so any data they may return is
// unusable. Thankfully, every request that hits the GitLab API has an
// `x-gitlab-meta` header, that's a strong enough indication that we're
// dealing with GitLab.

let uri = Url::parse(&format!("https://{}/api/v4/projects?per_page=1", &host))?;
let client = reqwest::Client::builder().user_agent(USER_AGENT).build()?;
let res = client
.get(uri)
.header(ACCEPT, "application/json")
.send()
.await?;
Ok(res.status() == 200 && res.headers().contains_key("x-gitlab-meta"))
}
2 changes: 2 additions & 0 deletions src/api/v1/gitlab/utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@

mod gitlab_api_get_latest_tag;
pub use self::gitlab_api_get_latest_tag::gitlab_api_get_latest_tag;
mod is_gitlab;
pub use self::is_gitlab::is_gitlab;
1 change: 1 addition & 0 deletions src/api/v1/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

pub mod routes;

mod auto_discovery;
mod flakehub;
mod forgejo;
mod github;
Expand Down
4 changes: 4 additions & 0 deletions src/api/v1/routes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
//
// SPDX-License-Identifier: AGPL-3.0-only

use super::auto_discovery::routes::get_routes as get_auto_discovery_routes;
use super::flakehub::routes::get_routes as get_flakehub_routes;
use super::forgejo::routes::get_redirect_routes as get_forgejo_redirect_routes;
use super::forgejo::routes::get_routes as get_forgejo_routes;
Expand All @@ -12,8 +13,11 @@ use axum::Router;

pub fn get_routes() -> Router {
Router::new()
.merge(get_auto_discovery_routes())
.nest("/github", get_github_routes())
.nest("/github.com", get_github_routes())
.nest("/flakehub", get_flakehub_routes())
.nest("/flakehub.com", get_flakehub_routes())
.nest("/forgejo", get_forgejo_routes())
.nest("/gitea", get_forgejo_routes())
.nest("/gitlab", get_gitlab_routes())
Expand Down