lockfile: Allow specifying multiple lockfiles

Teach `rpm-ostree compose tree` to accept multiple `--ex-lockfile`
arguments. In this case, later lockfiles can override the NEVRA for
packages specified in previous lockfiles.

This will be used in the FCOS pipeline, where we want to be able to have
a shared "base lockfile" and then stream-specific "override lockfiles".

I contemplated making this an `include: ...` key instead similar to the
manifests, but I'm not sure that paradigm fits as nicely for lockfiles.
Making it separate switches instead also makes it trivial to implement
in cosa.

(And of course, this is all still prefixed with `--ex` which means we
are at liberty of changing this interface later on after gaining some
experience with it).

Closes: #1867
Approved by: cgwalters
This commit is contained in:
Jonathan Lebon 2019-07-10 09:52:35 -04:00 committed by Atomic Bot
parent b670ab37c2
commit 1b43ad04be
3 changed files with 74 additions and 9 deletions

View File

@ -109,6 +109,24 @@ pub(crate) fn ffi_ptr_array_to_vec<T>(a: *mut glib_sys::GPtrArray) -> Vec<*mut T
v
}
/// Transform a NULL-terminated array of C strings to an allocated Vec holding unowned references.
/// This is similar to FromGlibPtrContainer::from_glib_none(), but avoids cloning elements.
pub(crate) fn ffi_strv_to_os_str_vec<'a>(mut strv: *mut *mut libc::c_char) -> Vec<&'a OsStr> {
assert!(!strv.is_null());
// In theory, we could use std::slice::from_raw_parts instead to make this more 0-cost and
// create an &[*mut libc::c_char], but from there there's no clean way to get a &['a OsStr]
// short of just transmuting.
let mut v = Vec::new();
unsafe {
while !(*strv).is_null() {
v.push(ffi_view_os_str(*strv));
strv = strv.offset(1);
}
}
v
}
// View `fd` as an `openat::Dir` instance. Lifetime of return value
// must be less than or equal to that of parameter.
pub(crate) fn ffi_view_openat_dir(fd: libc::c_int) -> openat::Dir {

View File

@ -23,6 +23,7 @@ use failure::Fallible;
use serde_derive::{Deserialize, Serialize};
use serde_json;
use std::collections::{HashMap, BTreeMap};
use std::iter::Extend;
use std::path::Path;
use std::io;
@ -54,6 +55,20 @@ fn lockfile_parse<P: AsRef<Path>>(filename: P,) -> Fallible<LockfileConfig> {
Ok(lf)
}
/// Given lockfile filenames, parse them. Later lockfiles may override packages from earlier ones.
fn lockfile_parse_multiple<P: AsRef<Path>>(filenames: &[P]) -> Fallible<LockfileConfig> {
let mut final_lockfile: Option<LockfileConfig> = None;
for filename in filenames {
let lf = lockfile_parse(filename)?;
if let Some(ref mut final_lockfile) = final_lockfile {
final_lockfile.merge(lf);
} else {
final_lockfile = Some(lf);
}
}
Ok(final_lockfile.expect("lockfile_parse: at least one lockfile"))
}
/// Lockfile format:
///
/// ```
@ -87,6 +102,12 @@ struct LockedPackage {
digest: String,
}
impl LockfileConfig {
fn merge(&mut self, other: LockfileConfig) {
self.packages.extend(other.packages);
}
}
#[cfg(test)]
mod tests {
use super::*;
@ -120,6 +141,33 @@ mod tests {
assert!(lockfile.packages.len() == 2);
}
static OVERRIDE_JS: &str = r###"
{
"packages": {
"foo": {
"evra": "2.0-2.noarch",
"digest": "sha256:dada"
}
}
}
"###;
#[test]
fn basic_valid_override() {
let mut base_input = io::BufReader::new(VALID_PRELUDE_JS.as_bytes());
let mut base_lockfile = lockfile_parse_stream(&mut base_input).unwrap();
assert!(base_lockfile.packages.len() == 2);
let mut override_input = io::BufReader::new(OVERRIDE_JS.as_bytes());
let override_lockfile = lockfile_parse_stream(&mut override_input).unwrap();
assert!(override_lockfile.packages.len() == 1);
base_lockfile.merge(override_lockfile);
assert!(base_lockfile.packages.len() == 2);
assert!(base_lockfile.packages.get("foo").unwrap().evra == "2.0-2.noarch");
assert!(base_lockfile.packages.get("bar").unwrap().evra == "0.8-15.x86_64");
}
#[test]
fn test_invalid() {
let mut input = io::BufReader::new(INVALID_PRELUDE_JS.as_bytes());
@ -156,12 +204,11 @@ mod ffi {
#[no_mangle]
pub extern "C" fn ror_lockfile_read(
filename: *const libc::c_char,
filenames: *mut *mut libc::c_char,
gerror: *mut *mut glib_sys::GError,
) -> *mut glib_sys::GHashTable {
// Convert arguments
let filename = ffi_view_os_str(filename);
match lockfile_parse(filename) {
let filenames = ffi_strv_to_os_str_vec(filenames);
match lockfile_parse_multiple(&filenames) {
Err(ref e) => {
error_to_glib(e, gerror);
ptr::null_mut()

View File

@ -78,7 +78,7 @@ static char *opt_write_commitid_to;
static char *opt_write_composejson_to;
static gboolean opt_no_parent;
static char *opt_write_lockfile;
static char *opt_read_lockfile;
static char **opt_read_lockfiles;
/* shared by both install & commit */
static GOptionEntry common_option_entries[] = {
@ -102,7 +102,7 @@ static GOptionEntry install_option_entries[] = {
{ "workdir", 0, 0, G_OPTION_ARG_STRING, &opt_workdir, "Working directory", "WORKDIR" },
{ "workdir-tmpfs", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &opt_workdir_tmpfs, "Use tmpfs for working state", NULL },
{ "ex-write-lockfile-to", 0, 0, G_OPTION_ARG_STRING, &opt_write_lockfile, "Write RPM versions information to FILE", "FILE" },
{ "ex-lockfile", 0, 0, G_OPTION_ARG_STRING, &opt_read_lockfile, "Read RPM version information from FILE", "FILE" },
{ "ex-lockfile", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_read_lockfiles, "Read RPM version information from FILE", "FILE" },
{ NULL }
};
@ -692,13 +692,13 @@ rpm_ostree_compose_context_new (const char *treefile_pathstr,
if (!self->corectx)
return FALSE;
if (opt_read_lockfile)
if (opt_read_lockfiles)
{
g_autoptr(GHashTable) map = ror_lockfile_read (opt_read_lockfile, error);
g_autoptr(GHashTable) map = ror_lockfile_read (opt_read_lockfiles, error);
if (!map)
return FALSE;
rpmostree_context_set_vlockmap (self->corectx, map);
g_print ("Loaded lockfile: %s\n", opt_read_lockfile);
g_print ("Loaded lockfiles:\n %s\n", g_strjoinv ("\n ", opt_read_lockfiles));
}
const char *arch = dnf_context_get_base_arch (rpmostree_context_get_dnf (self->corectx));