launchpad: drive the endpoints from data/launchpad.yml

This commit is contained in:
2026-09-18 13:27:20 +02:00
parent ead97e1213
commit 7af767898e
5 changed files with 117 additions and 14 deletions
+84 -8
View File
@@ -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
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
const API_BASE: &str = "https://api.launchpad.net/1.0";
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]