launchpad: drive the endpoints from data/launchpad.yml
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
## Launchpad service endpoints: the REST API, the PPA SFTP upload queue,
|
||||
## the PPA package-content host and the Ubuntu source-package git web UI.
|
||||
## Like host_keys.yml, this file exists so that static endpoints are data,
|
||||
## updatable in one reviewable place, instead of hardcoded in the source —
|
||||
## the API and content URLs were previously triplicated across modules.
|
||||
##
|
||||
## Templates carry their variable parts as {name} placeholders ({owner},
|
||||
## {ppa}, {package}), substituted by the accessors of src/launchpad.rs
|
||||
## with plain string replacement.
|
||||
##
|
||||
## Where the values come from:
|
||||
## api_base: the Launchpad REST API root (https://launchpad.net/docs/api/)
|
||||
## ssh_*: the PPA upload queue, as expanded by dput-ng's
|
||||
## ppa:user/ppa profile (ppa.launchpad.net:22, incoming
|
||||
## ~<user>/<ppa>)
|
||||
## content_host_template: ppa.launchpadcontent.net serves PPA apt
|
||||
## repositories since the 2022 move off ppa.launchpad.net
|
||||
## git_web_template: Launchpad's CGit mirrors of Ubuntu source packages
|
||||
## (git.launchpad.net/ubuntu/+source/<package>)
|
||||
|
||||
api_base: https://api.launchpad.net/1.0
|
||||
ssh_host: ppa.launchpad.net
|
||||
ssh_port: 22
|
||||
incoming_template: "~{owner}/{ppa}"
|
||||
content_host_template: https://ppa.launchpadcontent.net/{owner}/{ppa}/ubuntu
|
||||
git_web_template: https://git.launchpad.net/ubuntu/+source/{package}
|
||||
+2
-2
@@ -851,7 +851,7 @@ fn parse_ppa_url(ppa_base_url: &str) -> Option<(String, String)> {
|
||||
.strip_prefix("https://")
|
||||
.or_else(|| ppa_base_url.strip_prefix("http://"))?;
|
||||
let (host, path) = rest.split_once('/')?;
|
||||
if host != "ppa.launchpadcontent.net" {
|
||||
if host != crate::launchpad::ppa_content_host() {
|
||||
return None;
|
||||
}
|
||||
|
||||
@@ -886,7 +886,7 @@ pub async fn ppa_keyring_bytes(
|
||||
return Ok(cached.clone());
|
||||
}
|
||||
|
||||
let api_url = format!("https://api.launchpad.net/1.0/~{owner}/+archive/ubuntu/{name}");
|
||||
let api_url = crate::launchpad::archive_url(&owner, &name);
|
||||
let response = crate::distro_info::http_get_retried(&api_url).await?;
|
||||
if !response.status().is_success() {
|
||||
return Err(format!(
|
||||
|
||||
+3
-2
@@ -7,8 +7,9 @@
|
||||
//!
|
||||
//! This module is deliberately not a central registry: each file is
|
||||
//! embedded by the module that owns it (distro_info.rs owns
|
||||
//! data/distro_info.yml, put/ssh.rs owns data/host_keys.yml, quirks.rs
|
||||
//! owns data/quirks.yml) through the [`embed_data!`] macro below, so data
|
||||
//! data/distro_info.yml, launchpad.rs owns data/launchpad.yml, put/ssh.rs
|
||||
//! owns data/host_keys.yml, quirks.rs owns data/quirks.yml) through the
|
||||
//! [`embed_data!`] macro below, so data
|
||||
//! and its accessors stay together and a diff touching one domain cannot
|
||||
//! half-touch another. The macro embeds the file at compile time and
|
||||
//! parses it once into a `lazy_static` on first use; since the data ships
|
||||
|
||||
+84
-8
@@ -23,13 +23,69 @@ use std::path::Path;
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::data::embed_data;
|
||||
use crate::put::target::UploadTarget;
|
||||
|
||||
/// Git configuration key holding the Launchpad account name
|
||||
const LP_USER_KEY: &str = "lp.user";
|
||||
|
||||
/// Launchpad service endpoints, loaded from the bundled `launchpad.yml`
|
||||
/// data file (same pattern as `distro_info.yml`): static endpoints that
|
||||
/// change with Launchpad, not with the code, are data — several of them
|
||||
/// were previously duplicated across three modules.
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct LaunchpadData {
|
||||
/// Base URL of the Launchpad REST API
|
||||
const API_BASE: &str = "https://api.launchpad.net/1.0";
|
||||
api_base: String,
|
||||
/// Host of the PPA SFTP upload server
|
||||
ssh_host: String,
|
||||
/// Port of the PPA SFTP upload server
|
||||
ssh_port: u16,
|
||||
/// Upload queue incoming directory template (`{owner}`/`{ppa}`)
|
||||
incoming_template: String,
|
||||
/// PPA package-content (apt repository) URL template
|
||||
content_host_template: String,
|
||||
/// Ubuntu source-package git web URL template (`{package}`)
|
||||
git_web_template: String,
|
||||
}
|
||||
|
||||
embed_data! {
|
||||
static ref LAUNCHPAD_DATA: LaunchpadData = "../data/launchpad.yml"
|
||||
}
|
||||
|
||||
/// Base URL of the Launchpad REST API
|
||||
fn api_base() -> &'static str {
|
||||
&LAUNCHPAD_DATA.api_base
|
||||
}
|
||||
|
||||
/// Host serving PPA package content, derived from the content-host
|
||||
/// template so the URL builders and the URL parsers of PPA addresses
|
||||
/// cannot drift apart
|
||||
pub(crate) fn ppa_content_host() -> &'static str {
|
||||
let template = LAUNCHPAD_DATA.content_host_template.as_str();
|
||||
let after_scheme = template
|
||||
.split_once("://")
|
||||
.map_or(template, |(_, rest)| rest);
|
||||
after_scheme.split('/').next().unwrap_or(after_scheme)
|
||||
}
|
||||
|
||||
/// Base URL of the apt repository serving a PPA's packages
|
||||
/// (e.g. `https://ppa.launchpadcontent.net/user/ppa/ubuntu`)
|
||||
pub(crate) fn ppa_content_url(owner: &str, ppa: &str) -> String {
|
||||
LAUNCHPAD_DATA
|
||||
.content_host_template
|
||||
.replace("{owner}", owner)
|
||||
.replace("{ppa}", ppa)
|
||||
}
|
||||
|
||||
/// URL of the Launchpad git repository of an Ubuntu source package
|
||||
/// (`git.launchpad.net/ubuntu/+source/<package>`), the preferred VCS of
|
||||
/// Ubuntu packages
|
||||
pub(crate) fn ubuntu_source_git_url(package: &str) -> String {
|
||||
LAUNCHPAD_DATA
|
||||
.git_web_template
|
||||
.replace("{package}", package)
|
||||
}
|
||||
|
||||
/// Page size (`ws.size`) asked from Launchpad collections. Launchpad
|
||||
/// truncates collection answers at 75 entries by default and rejects
|
||||
@@ -88,27 +144,30 @@ fn split_ppa(ppa: &str) -> Result<(String, String), String> {
|
||||
|
||||
/// URL of the Launchpad API resource of a Launchpad account
|
||||
fn person_url(user: &str) -> String {
|
||||
format!("{API_BASE}/~{user}")
|
||||
format!("{}/~{user}", api_base())
|
||||
}
|
||||
|
||||
/// URL of the Launchpad API resource of a PPA (`~user/+archive/ubuntu/name`
|
||||
/// covers the default `ppa` archive and named archives alike); shared by the
|
||||
/// put-side pre-flight checks and the apt keyring's fingerprint lookup
|
||||
pub(crate) fn archive_url(user: &str, ppa: &str) -> String {
|
||||
format!("{API_BASE}/~{user}/+archive/ubuntu/{ppa}")
|
||||
format!("{}/~{user}/+archive/ubuntu/{ppa}", api_base())
|
||||
}
|
||||
|
||||
/// Resolve a `user/ppa_name` PPA argument into its upload target
|
||||
/// (`ppa.launchpad.net`, incoming `~user/ppa_name`), like dput-ng's
|
||||
/// Resolve a `user/ppa_name` PPA argument into its upload target (the
|
||||
/// SFTP host and incoming template of `launchpad.yml`), like dput-ng's
|
||||
/// `ppa:user/ppa` profile expansion.
|
||||
pub fn ppa_target(ppa: &str) -> Result<UploadTarget, String> {
|
||||
let (user, name) = split_ppa(ppa)?;
|
||||
|
||||
Ok(UploadTarget {
|
||||
fqdn: "ppa.launchpad.net".to_string(),
|
||||
port: 22,
|
||||
fqdn: LAUNCHPAD_DATA.ssh_host.clone(),
|
||||
port: LAUNCHPAD_DATA.ssh_port,
|
||||
login: None,
|
||||
incoming: format!("~{user}/{name}"),
|
||||
incoming: LAUNCHPAD_DATA
|
||||
.incoming_template
|
||||
.replace("{owner}", &user)
|
||||
.replace("{ppa}", &name),
|
||||
label: format!("ppa:{ppa}"),
|
||||
})
|
||||
}
|
||||
@@ -425,6 +484,23 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
/// The data-driven endpoint accessors build the same addresses the
|
||||
/// former hardcoded constants carried (each verified against the live
|
||||
/// service), and the content host is derived from the same template
|
||||
/// the content URLs are built from
|
||||
#[test]
|
||||
fn data_driven_endpoints_match_the_service() {
|
||||
assert_eq!(
|
||||
ppa_content_url("vhaudiquet", "noctalia"),
|
||||
"https://ppa.launchpadcontent.net/vhaudiquet/noctalia/ubuntu"
|
||||
);
|
||||
assert_eq!(ppa_content_host(), "ppa.launchpadcontent.net");
|
||||
assert_eq!(
|
||||
ubuntu_source_git_url("hello"),
|
||||
"https://git.launchpad.net/ubuntu/+source/hello"
|
||||
);
|
||||
}
|
||||
|
||||
/// The API answer carries many unrelated fields; deserialization must
|
||||
/// pick the relevant ones and tolerate a null `enabled`
|
||||
#[test]
|
||||
|
||||
+2
-2
@@ -18,11 +18,11 @@ use log::{debug, warn};
|
||||
/// # Returns
|
||||
/// * The base URL for the PPA (e.g., "https://ppa.launchpadcontent.net/user/ppa_name/ubuntu/")
|
||||
pub fn ppa_to_base_url(user: &str, name: &str) -> String {
|
||||
format!("https://ppa.launchpadcontent.net/{}/{}/ubuntu", user, name)
|
||||
crate::launchpad::ppa_content_url(user, name)
|
||||
}
|
||||
|
||||
fn check_launchpad_repo_sync(package: &str) -> Result<Option<String>, String> {
|
||||
let url = format!("https://git.launchpad.net/ubuntu/+source/{}", package);
|
||||
let url = crate::launchpad::ubuntu_source_git_url(package);
|
||||
|
||||
// Use libgit2 to check if the remote repository exists
|
||||
// This is more reliable than HTTP HEAD requests when CGIt is disabled
|
||||
|
||||
Reference in New Issue
Block a user