Change-Id: Ifadd190e10ec22570ab3ccb4df54f64ae5ef0a44 Reviewed-on: https://cl.tvl.fyi/c/depot/+/12674 Tested-by: BuildkiteCI Reviewed-by: flokli <flokli@flokli.de>
144 lines
4.9 KiB
Rust
144 lines
4.9 KiB
Rust
//! Module to create an OCI runtime bundle for a given [BuildRequest].
|
|
use std::{
|
|
fs,
|
|
path::{Path, PathBuf},
|
|
};
|
|
|
|
use super::scratch_name;
|
|
use crate::buildservice::BuildRequest;
|
|
use anyhow::{bail, Context};
|
|
use tracing::{debug, instrument};
|
|
|
|
/// Produce an OCI bundle in a given path.
|
|
/// Check [make_spec] for a description about the paths produced.
|
|
#[instrument(err)]
|
|
pub(crate) fn make_bundle<'a>(
|
|
request: &BuildRequest,
|
|
runtime_spec: &oci_spec::runtime::Spec,
|
|
path: &Path,
|
|
) -> anyhow::Result<()> {
|
|
fs::create_dir_all(path).context("failed to create bundle path")?;
|
|
|
|
let spec_json = serde_json::to_string(runtime_spec).context("failed to render spec to json")?;
|
|
fs::write(path.join("config.json"), spec_json).context("failed to write config.json")?;
|
|
|
|
fs::create_dir_all(path.join("inputs")).context("failed to create inputs dir")?;
|
|
|
|
let root_path = path.join("root");
|
|
|
|
fs::create_dir_all(&root_path).context("failed to create root path dir")?;
|
|
fs::create_dir_all(root_path.join("etc")).context("failed to create root/etc dir")?;
|
|
|
|
// TODO: populate /etc/{group,passwd}. It's a mess?
|
|
|
|
let scratch_root = path.join("scratch");
|
|
fs::create_dir_all(&scratch_root).context("failed to create scratch/ dir")?;
|
|
|
|
// for each scratch path, calculate its name inside scratch, and ensure the
|
|
// directory exists.
|
|
for p in request.scratch_paths.iter() {
|
|
let scratch_path = scratch_root.join(scratch_name(p));
|
|
debug!(scratch_path=?scratch_path, path=?p, "about to create scratch dir");
|
|
fs::create_dir_all(scratch_path).context("Unable to create scratch dir")?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Determine the path of all outputs specified in a [BuildRequest]
|
|
/// as seen from the host, for post-build ingestion.
|
|
/// This lookup needs to take scratch paths into consideration, as the build
|
|
/// root is not writable on its own.
|
|
/// If a path can't be determined, an error is returned.
|
|
pub(crate) fn get_host_output_paths(
|
|
request: &BuildRequest,
|
|
bundle_path: &Path,
|
|
) -> anyhow::Result<Vec<PathBuf>> {
|
|
let scratch_root = bundle_path.join("scratch");
|
|
|
|
let mut host_output_paths: Vec<PathBuf> = Vec::with_capacity(request.outputs.len());
|
|
|
|
for output_path in request.outputs.iter() {
|
|
// calculate the location of the path.
|
|
if let Some((mp, relpath)) = find_path_in_scratchs(output_path, &request.scratch_paths) {
|
|
host_output_paths.push(scratch_root.join(scratch_name(mp)).join(relpath));
|
|
} else {
|
|
bail!("unable to find path {output_path:?}");
|
|
}
|
|
}
|
|
|
|
Ok(host_output_paths)
|
|
}
|
|
|
|
/// For a given list of mountpoints (sorted) and a search_path, find the
|
|
/// specific mountpoint parenting that search_path and return it, as well as the
|
|
/// relative path from there to the search_path.
|
|
/// mountpoints must be sorted, so we can iterate over the list from the back
|
|
/// and match on the prefix.
|
|
fn find_path_in_scratchs<'a, 'b, I>(
|
|
search_path: &'a Path,
|
|
mountpoints: I,
|
|
) -> Option<(&'b Path, &'a Path)>
|
|
where
|
|
I: IntoIterator<Item = &'b PathBuf>,
|
|
I::IntoIter: DoubleEndedIterator,
|
|
{
|
|
mountpoints
|
|
.into_iter()
|
|
.rev()
|
|
.find_map(|mp| Some((mp.as_path(), search_path.strip_prefix(mp).ok()?)))
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use std::path::{Path, PathBuf};
|
|
|
|
use rstest::rstest;
|
|
|
|
use crate::{buildservice::BuildRequest, oci::scratch_name};
|
|
|
|
use super::{find_path_in_scratchs, get_host_output_paths};
|
|
|
|
#[rstest]
|
|
#[case::simple("nix/store/aaaa", &["nix/store".into()], Some(("nix/store", "aaaa")))]
|
|
#[case::prefix_no_sep("nix/store/aaaa", &["nix/sto".into()], None)]
|
|
#[case::not_found("nix/store/aaaa", &["build".into()], None)]
|
|
fn test_test_find_path_in_scratchs(
|
|
#[case] search_path: &str,
|
|
#[case] mountpoints: &[String],
|
|
#[case] expected: Option<(&str, &str)>,
|
|
) {
|
|
let expected = expected.map(|e| (Path::new(e.0), Path::new(e.1)));
|
|
assert_eq!(
|
|
find_path_in_scratchs(
|
|
Path::new(search_path),
|
|
mountpoints
|
|
.iter()
|
|
.map(PathBuf::from)
|
|
.collect::<Vec<_>>()
|
|
.as_slice()
|
|
),
|
|
expected
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_get_host_output_paths_simple() {
|
|
let request = BuildRequest {
|
|
outputs: vec!["nix/store/fhaj6gmwns62s6ypkcldbaj2ybvkhx3p-foo".into()],
|
|
scratch_paths: vec!["build".into(), "nix/store".into()],
|
|
..Default::default()
|
|
};
|
|
|
|
let paths =
|
|
get_host_output_paths(&request, Path::new("bundle-root")).expect("must succeed");
|
|
|
|
let mut expected_path = PathBuf::new();
|
|
expected_path.push("bundle-root");
|
|
expected_path.push("scratch");
|
|
expected_path.push(scratch_name(Path::new("nix/store")));
|
|
expected_path.push("fhaj6gmwns62s6ypkcldbaj2ybvkhx3p-foo");
|
|
|
|
assert_eq!(vec![expected_path], paths)
|
|
}
|
|
}
|