Skip to content

Commit

Permalink
extensions: Add support for development extensions
Browse files Browse the repository at this point in the history
In RHCOS, we ship kernel development-related packages as an extension.
Those aren't really extensions that are meant to be layered onto the
host.  They're meant to be used in a build environment somewhere to
compile kernel modules.

This makes it very different from "OS extensions" in at least two
drastic ways:
1. we don't want to do any depsolving (e.g. we don't want to pull in
   `gcc` or something)
2. some of those packages may be present in the base already, but we
   still want to redownload them

Hesitated putting this functionality in rpm-ostree, but I think in the
end it cuts from the benefit of moving this code to rpm-ostree if we
can't entirely get rid of the Python script it obsoletes. Plus, being
able to use the `match-base-evr` is still really useful for this use
case.

Let's add a new `kind` key to support this. The traditional extensions
are called "OS extensions" and these new extensions are called
"development extensions".

The latter is not yet part of the state checksum, so change detection
doesn't work there. I think that's fine for now though because the
primary use case is the kernel, and there we want to match the base
version. So if the kernel changes, the base would change too. (Though
there's the corner case of adding a new package to the list while at the
same version...)
  • Loading branch information
jlebon committed Jan 29, 2021
1 parent 76c4e3f commit cc197bb
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 12 deletions.
60 changes: 52 additions & 8 deletions rust/src/extensions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@ pub struct Extension {
architectures: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
match_base_evr: Option<String>,
kind: ExtensionKind,
}

#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
enum ExtensionKind {
OsExtension,
Development,
}

fn extensions_load_stream(
Expand All @@ -55,9 +63,11 @@ fn extensions_load_stream(
.collect();

for (_, ext) in parsed.extensions.iter_mut() {
for pkg in &ext.packages {
if base_pkgs.contains_key(pkg.as_str()) {
bail!("package {} already present in base", pkg);
if ext.kind == ExtensionKind::OsExtension {
for pkg in &ext.packages {
if base_pkgs.contains_key(pkg.as_str()) {
bail!("package {} already present in base", pkg);
}
}
}
if let Some(ref matched_base_pkg) = ext.match_base_evr {
Expand Down Expand Up @@ -91,9 +101,18 @@ impl Extensions {
self.repos.as_ref().map(|v| v.clone()).unwrap_or_default()
}

pub(crate) fn get_packages(&self) -> Vec<String> {
pub(crate) fn get_os_extension_packages(&self) -> Vec<String> {
self.extensions
.iter()
.filter(|(_, ext)| ext.kind == ExtensionKind::OsExtension)
.flat_map(|(_, ext)| ext.packages.iter().cloned())
.collect()
}

pub(crate) fn get_development_packages(&self) -> Vec<String> {
self.extensions
.iter()
.filter(|(_, ext)| ext.kind == ExtensionKind::Development)
.flat_map(|(_, ext)| ext.packages.iter().cloned())
.collect()
}
Expand Down Expand Up @@ -152,11 +171,12 @@ extensions:
bazboo:
packages:
- bazboo
kind: os-extension
"###;
let mut input = std::io::BufReader::new(buf.as_bytes());
let extensions = extensions_load_stream(&mut input, "x86_64", &base_rpmdb()).unwrap();
assert!(extensions.get_repos() == vec!["my-repo"]);
assert!(extensions.get_packages() == vec!["bazboo"]);
assert!(extensions.get_os_extension_packages() == vec!["bazboo"]);
}

#[test]
Expand All @@ -166,6 +186,7 @@ extensions:
foobar:
packages:
- foobar
kind: os-extension
"###;
let mut input = std::io::BufReader::new(buf.as_bytes());
match extensions_load_stream(&mut input, "x86_64", &base_rpmdb()) {
Expand All @@ -174,6 +195,20 @@ extensions:
}
}

#[test]
fn ext_in_devel() {
let buf = r###"
extensions:
foobar:
packages:
- foobar
kind: development
"###;
let mut input = std::io::BufReader::new(buf.as_bytes());
let extensions = extensions_load_stream(&mut input, "x86_64", &base_rpmdb()).unwrap();
assert!(extensions.get_development_packages() == vec!["foobar"]);
}

#[test]
fn basearch_filter() {
let buf = r###"
Expand All @@ -183,19 +218,21 @@ extensions:
- bazboo
architectures:
- x86_64
kind: os-extension
dodo:
packages:
- dodo
- dada
architectures:
- s390x
kind: os-extension
"###;
let mut input = std::io::BufReader::new(buf.as_bytes());
let extensions = extensions_load_stream(&mut input, "x86_64", &base_rpmdb()).unwrap();
assert!(extensions.get_packages() == vec!["bazboo"]);
assert!(extensions.get_os_extension_packages() == vec!["bazboo"]);
let mut input = std::io::BufReader::new(buf.as_bytes());
let extensions = extensions_load_stream(&mut input, "s390x", &base_rpmdb()).unwrap();
assert!(extensions.get_packages() == vec!["dodo", "dada"]);
assert!(extensions.get_os_extension_packages() == vec!["dodo", "dada"]);
}

#[test]
Expand All @@ -206,9 +243,16 @@ extensions:
packages:
- foobar-ext
match-base-evr: foobar
kind: os-extension
devel:
packages:
- foobar-devel
match-base-evr: foobar
kind: development
"###;
let mut input = std::io::BufReader::new(buf.as_bytes());
let extensions = extensions_load_stream(&mut input, "x86_64", &base_rpmdb()).unwrap();
assert!(extensions.get_packages() == vec!["foobar-ext-1.2-3"]);
assert!(extensions.get_os_extension_packages() == vec!["foobar-ext-1.2-3"]);
assert!(extensions.get_development_packages() == vec!["foobar-devel-1.2-3"]);
}
}
3 changes: 2 additions & 1 deletion rust/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,8 @@ mod ffi {
base_pkgs: &Vec<StringMapping>,
) -> Result<Box<Extensions>>;
fn get_repos(&self) -> Vec<String>;
fn get_packages(&self) -> Vec<String>;
fn get_os_extension_packages(&self) -> Vec<String>;
fn get_development_packages(&self) -> Vec<String>;
fn state_checksum_changed(&self, chksum: &str, output_dir: &str) -> Result<bool>;
fn update_state_checksum(&self, chksum: &str, output_dir: &str) -> Result<()>;
fn serialize_to_dir(&self, output_dir: &str) -> Result<()>;
Expand Down
40 changes: 39 additions & 1 deletion src/app/rpmostree-compose-builtin-tree.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -1561,7 +1561,7 @@ rpmostree_compose_builtin_extensions (int argc,

g_autoptr(RpmOstreeTreespec) spec = NULL;
{ g_autoptr(GPtrArray) gpkgs = g_ptr_array_new_with_free_func (g_free);
auto pkgs = extensions->get_packages();
auto pkgs = extensions->get_os_extension_packages();
for (auto pkg : pkgs)
g_ptr_array_add (gpkgs, (gpointer*) g_strdup (pkg.c_str()));

Expand Down Expand Up @@ -1622,6 +1622,44 @@ rpmostree_compose_builtin_extensions (int argc,
return FALSE;
}

/* This is hacky: for "development" extensions, we don't want any depsolving
* against the base OS. Rather than awkwardly teach the core about this, we
* just reuse its sack and keep all the functionality here. */

DnfContext *dnfctx = rpmostree_context_get_dnf (ctx);
DnfSack *sack = dnf_context_get_sack (dnfctx);

/* disable the system repo; we always want to download, even if already in the base */
dnf_sack_repo_enabled(sack, HY_SYSTEM_REPO_NAME, 0);

auto pkgs = extensions->get_development_packages();
g_autoptr(GPtrArray) devel_pkgs_to_download =
g_ptr_array_new_with_free_func (g_object_unref);
for (auto pkg : pkgs)
{
g_autoptr(GPtrArray) matches = rpmostree_get_matching_packages (sack, pkg.c_str());
if (matches->len == 0)
return glnx_throw (error, "Package %s not found", pkg.c_str());
DnfPackage *found_pkg = (DnfPackage*)matches->pdata[0];
g_ptr_array_add (devel_pkgs_to_download, g_object_ref (found_pkg));
}

rpmostree_set_repos_on_packages (dnfctx, devel_pkgs_to_download);

if (!rpmostree_download_packages (devel_pkgs_to_download, cancellable, error))
return FALSE;

for (guint i = 0; i < devel_pkgs_to_download->len; i++)
{
DnfPackage *pkg = (DnfPackage*)devel_pkgs_to_download->pdata[i];
const char *src = dnf_package_get_filename (pkg);
const char *basename = glnx_basename (src);
if (!glnx_file_copy_at (AT_FDCWD, dnf_package_get_filename (pkg), NULL, output_dfd,
basename, GLNX_FILE_COPY_NOXATTRS, cancellable, error))
return FALSE;
}

// XXX: account for development extensions
extensions->update_state_checksum (state_checksum, opt_extensions_output_dir);
extensions->serialize_to_dir (opt_extensions_output_dir);
if (!process_touch_if_changed (error))
Expand Down
25 changes: 23 additions & 2 deletions tests/compose/test-basic-unified.sh
Original file line number Diff line number Diff line change
Expand Up @@ -89,17 +89,36 @@ build_rpm dodo-base
build_rpm dodo requires dodo-base
build_rpm solitaire

# this is pretty terrible... need --json for `rpm-ostree db list`
kernel_vra=$(rpm-ostree db list --repo=${repo} ${treeref} kernel | tail -n1 | cut -d- -f2-)
kernel_v=$(cut -d- -f1 <<< "$kernel_evr")
kernel_ra=$(cut -d- -f2- <<< "$kernel_evr")
kernel_r=${kernel_ra%.x86_64}

build_rpm kernel-core version ${kernel_v} release ${kernel_r}
build_rpm kernel-devel version ${kernel_v} release ${kernel_r}
build_rpm kernel-headers version ${kernel_v} release ${kernel_r}

cat > extensions.yaml << EOF
extensions:
extinct-birds:
kind: os-extension
packages:
- dodo
- solitaire
another-arch:
kind: os-extension
packages:
- nonexistent
architectures:
- badarch
kernel-devel:
kind: development
packages:
- kernel-core
- kernel-devel
- kernel-headers
match-base-evr: kernel
EOF

# we don't actually need root here, but in CI the cache may be in a qcow2 and
Expand All @@ -110,10 +129,12 @@ runasroot rpm-ostree compose extensions --repo=${repo} \
--touch-if-changed extensions-changed

ls extensions/{dodo-1.0,dodo-base-1.0,solitaire-1.0}-*.rpm
ls extensions/kernel-{core,devel,headers}-${kernel_v}-${kernel_r}.x86_64.rpm
test -f extensions-changed
assert_jq extensions/extensions.json \
'.extensions|length == 1' \
'.extensions["extinct-birds"]'
'.extensions|length == 2' \
'.extensions["extinct-birds"]' \
'.extensions["kernel-devel"]'
echo "ok extensions"

rm extensions-changed
Expand Down

0 comments on commit cc197bb

Please sign in to comment.