Add fedora-integration: Support override replace https://bodhi/...
This adds support for e.g.: ``` $ rpm-ostree override replace https://bodhi.fedoraproject.org/updates/FEDORA-2020-2908628031 ``` This will find the Koji builds from the listed update, download all the RPMs (that aren't debuginfo) and pass them for overrides in the same way we support `override replace http://somewebserver/foo.rpm` now. We also support directly linking a Koji build: ``` $ rpm-ostree override replace https://koji.fedoraproject.org/koji/buildinfo?buildID=1625029 ``` Bodhi has a modern HTTP+JSON API, and the lack of a Koji equivalent drove me to create https://github.com/cgwalters/koji-sane-json-api and we currently depend on an instance set up in the OpenShift CI cluster. I hope it shouldn't take long to deploy this in Fedora Infra, but I don't want to block on it. Also notably this still downloads *all* the other RPMs even ones that aren't installed. Handling that truly correctly would require moving this logic to the daemon and core. All of this functionality is keyed off a `cfg(feature = "fedora-integration")` that is detected by a Rust `build.rs` which parses the build environment's `/etc/os-release` for now.
This commit is contained in:
parent
1f408bd396
commit
29d051e895
@ -40,6 +40,7 @@ libdnf-sys = { path = "rust/libdnf-sys", version = "0.1.0" }
|
|||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
cbindgen = "0.16.0"
|
cbindgen = "0.16.0"
|
||||||
|
anyhow = "1.0"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "rpmostree_rust"
|
name = "rpmostree_rust"
|
||||||
@ -58,5 +59,6 @@ lto = true
|
|||||||
|
|
||||||
[features]
|
[features]
|
||||||
sqlite-rpmdb-default = []
|
sqlite-rpmdb-default = []
|
||||||
|
fedora-integration = []
|
||||||
|
|
||||||
default = []
|
default = []
|
||||||
|
21
build.rs
Normal file
21
build.rs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
|
fn detect_fedora_feature() -> Result<()> {
|
||||||
|
if !std::path::Path::new("/usr/lib/os-release").exists() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
let p = std::process::Command::new("sh")
|
||||||
|
.args(&["-c", ". /usr/lib/os-release && echo ${ID}"])
|
||||||
|
.stdout(std::process::Stdio::piped())
|
||||||
|
.output()?;
|
||||||
|
let out = std::str::from_utf8(&p.stdout).ok().map(|s| s.trim());
|
||||||
|
if out == Some("fedora") {
|
||||||
|
println!(r#"cargo:rustc-cfg=feature="fedora-integration""#)
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
detect_fedora_feature()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
@ -10,7 +10,12 @@ fn is_http(arg: &str) -> bool {
|
|||||||
/// RPM URLs we need to fetch, and if so download those URLs and return file
|
/// RPM URLs we need to fetch, and if so download those URLs and return file
|
||||||
/// descriptors for the content.
|
/// descriptors for the content.
|
||||||
/// TODO(cxx-rs): This would be slightly more elegant as Result<Option<Vec<i32>>>
|
/// TODO(cxx-rs): This would be slightly more elegant as Result<Option<Vec<i32>>>
|
||||||
pub(crate) fn client_handle_fd_argument(arg: &str, _arch: &str) -> Result<Vec<i32>> {
|
pub(crate) fn client_handle_fd_argument(arg: &str, arch: &str) -> Result<Vec<i32>> {
|
||||||
|
#[cfg(feature = "fedora-integration")]
|
||||||
|
if let Some(fds) = crate::fedora_integration::handle_cli_arg(arg, arch)? {
|
||||||
|
return Ok(fds.into_iter().map(|f| f.into_raw_fd()).collect());
|
||||||
|
}
|
||||||
|
|
||||||
if is_http(arg) {
|
if is_http(arg) {
|
||||||
utils::download_url_to_tmpfile(arg, true).map(|f| vec![f.into_raw_fd()])
|
utils::download_url_to_tmpfile(arg, true).map(|f| vec![f.into_raw_fd()])
|
||||||
} else if arg.ends_with(".rpm") {
|
} else if arg.ends_with(".rpm") {
|
||||||
|
175
rust/src/fedora_integration.rs
Normal file
175
rust/src/fedora_integration.rs
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
use anyhow::{Context, Result};
|
||||||
|
use serde_derive::Deserialize;
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use std::fs::File;
|
||||||
|
|
||||||
|
const KOJI_URL_PREFIX: &str = "https://koji.fedoraproject.org/koji/";
|
||||||
|
/// See https://github.com/cgwalters/koji-sane-json-api
|
||||||
|
const KOJI_JSON_API_URL: &str = "kojiproxy-coreos.svc.ci.openshift.org";
|
||||||
|
const BODHI_URL_PREFIX: &str = "https://bodhi.fedoraproject.org/updates/";
|
||||||
|
const BODHI_UPDATE_PREFIX: &str = "FEDORA-";
|
||||||
|
|
||||||
|
mod bodhi {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub(crate) struct BodhiKojiBuild {
|
||||||
|
pub(crate) nvr: String,
|
||||||
|
/// Not currently included in koji URLs, so we ignore it
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub(crate) epoch: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub(crate) struct BodhiUpdate {
|
||||||
|
pub(crate) builds: Vec<BodhiKojiBuild>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct BodhiUpdateResponse {
|
||||||
|
update: BodhiUpdate,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn canonicalize_update_id(id: &str) -> Cow<str> {
|
||||||
|
let id = match id.strip_prefix(BODHI_URL_PREFIX) {
|
||||||
|
Some(s) => return Cow::Borrowed(s),
|
||||||
|
None => id,
|
||||||
|
};
|
||||||
|
match id.strip_prefix(BODHI_UPDATE_PREFIX) {
|
||||||
|
Some(_) => Cow::Borrowed(id),
|
||||||
|
None => Cow::Owned(format!("{}{}", BODHI_UPDATE_PREFIX, id)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn get_update(updateid: &str) -> Result<BodhiUpdate> {
|
||||||
|
let updateid = canonicalize_update_id(updateid);
|
||||||
|
let url = format!("{}{}", BODHI_URL_PREFIX, updateid);
|
||||||
|
let update_data = crate::utils::download_url_to_tmpfile(&url, false)
|
||||||
|
.context("Failed to download bodhi update info")?;
|
||||||
|
let resp: BodhiUpdateResponse = serde_json::from_reader(update_data)?;
|
||||||
|
Ok(resp.update)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn get_rpm_urls_from_update(updateid: &str, arch: &str) -> Result<Vec<String>> {
|
||||||
|
let update = bodhi::get_update(updateid)?;
|
||||||
|
update.builds.iter().try_fold(Vec::new(), |mut r, buildid| {
|
||||||
|
// For now hardcode skipping debuginfo because it's large and hopefully
|
||||||
|
// people aren't layering that.
|
||||||
|
let rpms = koji::get_rpm_urls_from_build(&buildid.nvr, arch, true)?;
|
||||||
|
r.extend(rpms);
|
||||||
|
Ok(r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_canonicalize() {
|
||||||
|
let regid = "FEDORA-2020-053d8a2e94";
|
||||||
|
let shortid = "2020-053d8a2e94";
|
||||||
|
let url = "https://bodhi.fedoraproject.org/updates/FEDORA-2020-053d8a2e94";
|
||||||
|
assert_eq!(canonicalize_update_id(regid), regid);
|
||||||
|
assert_eq!(canonicalize_update_id(url), regid);
|
||||||
|
assert_eq!(canonicalize_update_id(shortid), regid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod koji {
|
||||||
|
use super::*;
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
#[derive(Default, Deserialize)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub(crate) struct KojiBuildInfo {
|
||||||
|
nvr: String,
|
||||||
|
id: u64,
|
||||||
|
kojipkgs_url_prefix: String,
|
||||||
|
rpms: BTreeMap<String, Vec<String>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KojiBuildInfo {
|
||||||
|
fn rpmurl(&self, arch: &str, rpm: &str) -> String {
|
||||||
|
format!("{}/{}/{}", self.kojipkgs_url_prefix, arch, rpm)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn get_buildid_from_url(url: &str) -> Result<&str> {
|
||||||
|
let id = url.rsplit('?').next().expect("split");
|
||||||
|
match id.strip_prefix("buildID=") {
|
||||||
|
Some(s) => Ok(s),
|
||||||
|
None => anyhow::bail!("Failed to parse Koji buildid from URL {}", url),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn get_build(buildid: &str) -> Result<KojiBuildInfo> {
|
||||||
|
let url = format!("{}/buildinfo/{}", KOJI_JSON_API_URL, buildid);
|
||||||
|
let f = crate::utils::download_url_to_tmpfile(&url, false)
|
||||||
|
.context("Failed to download buildinfo from koji proxy")?;
|
||||||
|
Ok(serde_json::from_reader(std::io::BufReader::new(f))?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn get_rpm_urls_from_build(
|
||||||
|
buildid: &str,
|
||||||
|
arch: &str,
|
||||||
|
skip_debug: bool,
|
||||||
|
) -> Result<impl IntoIterator<Item = String>> {
|
||||||
|
let build = get_build(buildid)?;
|
||||||
|
let mut ret = Vec::new();
|
||||||
|
if let Some(rpms) = build.rpms.get(arch) {
|
||||||
|
ret.extend(
|
||||||
|
rpms.iter()
|
||||||
|
.filter(|r| !(skip_debug && is_debug_rpm(r)))
|
||||||
|
.map(|r| build.rpmurl(arch, r)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if let Some(rpms) = build.rpms.get("noarch") {
|
||||||
|
ret.extend(rpms.iter().map(|r| build.rpmurl("noarch", r)));
|
||||||
|
}
|
||||||
|
Ok(ret.into_iter())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_url_buildid() -> Result<()> {
|
||||||
|
assert_eq!(
|
||||||
|
get_buildid_from_url(
|
||||||
|
"https://koji.fedoraproject.org/koji/buildinfo?buildID=1637715"
|
||||||
|
)?,
|
||||||
|
"1637715"
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_debug_rpm(rpm: &str) -> bool {
|
||||||
|
rpm.contains("-debuginfo-") || rpm.contains("-debugsource-")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn handle_cli_arg(url: &str, arch: &str) -> Result<Option<Vec<File>>> {
|
||||||
|
if url.starts_with(BODHI_URL_PREFIX) {
|
||||||
|
let urls = bodhi::get_rpm_urls_from_update(url, arch)?;
|
||||||
|
Ok(Some(
|
||||||
|
crate::utils::download_urls_to_tmpfiles(urls, true)
|
||||||
|
.context("Failed to download RPMs")?,
|
||||||
|
))
|
||||||
|
} else if url.starts_with(KOJI_URL_PREFIX) {
|
||||||
|
let buildid = koji::get_buildid_from_url(url)?;
|
||||||
|
let urls: Vec<String> = koji::get_rpm_urls_from_build(&buildid, arch, true)?
|
||||||
|
.into_iter()
|
||||||
|
.collect();
|
||||||
|
Ok(Some(
|
||||||
|
crate::utils::download_urls_to_tmpfiles(urls, true)
|
||||||
|
.context("Failed to download RPMs")?,
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
@ -118,6 +118,8 @@ mod composepost;
|
|||||||
pub(crate) use composepost::*;
|
pub(crate) use composepost::*;
|
||||||
mod core;
|
mod core;
|
||||||
use crate::core::*;
|
use crate::core::*;
|
||||||
|
#[cfg(feature = "fedora-integration")]
|
||||||
|
mod fedora_integration;
|
||||||
mod history;
|
mod history;
|
||||||
pub use self::history::*;
|
pub use self::history::*;
|
||||||
mod journal;
|
mod journal;
|
||||||
|
20
tests/kolainst/destructive/layering-fedorainfra
Executable file
20
tests/kolainst/destructive/layering-fedorainfra
Executable file
@ -0,0 +1,20 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# kola: { "tags": "needs-internet" }
|
||||||
|
# Test https://github.com/coreos/rpm-ostree/pull/2420
|
||||||
|
# i.e. using overrides from Fedora Infrastructure tools (koji/bodhi)
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
. ${KOLA_EXT_DATA}/libtest.sh
|
||||||
|
cd $(mktemp -d)
|
||||||
|
|
||||||
|
# bodhi update for rpm-ostree (Fedora 33)
|
||||||
|
rpm-ostree override replace https://bodhi.fedoraproject.org/updates/FEDORA-2020-6e743def1d
|
||||||
|
rpm-ostree status > status.txt
|
||||||
|
assert_file_has_content_literal status.txt "Diff: 2 downgraded"
|
||||||
|
rpm-ostree cleanup -p
|
||||||
|
# Same build directly via Koji
|
||||||
|
rpm-ostree override replace https://koji.fedoraproject.org/koji/buildinfo?buildID=1637715
|
||||||
|
rpm-ostree status > status.txt
|
||||||
|
assert_file_has_content_literal status.txt "Diff: 2 downgraded"
|
||||||
|
|
||||||
|
echo "ok"
|
Loading…
Reference in New Issue
Block a user