Skip to content

Commit

Permalink
Add install-to-filesystem
Browse files Browse the repository at this point in the history
This will address the use case of having external code set up
the block devices and (empty) filesystems.  For example, one could
use a different privileged container, Anaconda, etc.

Closes: containers#54

Signed-off-by: Colin Walters <walters@verbum.org>
  • Loading branch information
cgwalters committed Feb 5, 2023
1 parent adf16d5 commit 8078700
Show file tree
Hide file tree
Showing 10 changed files with 427 additions and 29 deletions.
1 change: 1 addition & 0 deletions lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ libc = "^0.2"
once_cell = "1.9"
openssl = "^0.10"
nix = ">= 0.24, < 0.26"
regex = "1.7.1"
serde = { features = ["derive"], version = "1.0.125" }
serde_json = "1.0.64"
serde_with = ">= 1.9.4, < 2"
Expand Down
66 changes: 65 additions & 1 deletion lib/src/blockdev.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ use anyhow::{anyhow, Context, Result};
use camino::Utf8Path;
use fn_error_context::context;
use nix::errno::Errno;
use once_cell::sync::Lazy;
use regex::Regex;
use serde::Deserialize;
use std::collections::HashMap;
use std::fs::File;
use std::os::unix::io::AsRawFd;
use std::process::Command;
Expand Down Expand Up @@ -39,7 +42,7 @@ impl Device {

pub(crate) fn wipefs(dev: &Utf8Path) -> Result<()> {
Task::new_and_run(
&format!("Wiping device {dev}"),
format!("Wiping device {dev}"),
"wipefs",
["-a", dev.as_str()],
)
Expand Down Expand Up @@ -109,6 +112,67 @@ pub(crate) fn reread_partition_table(file: &mut File, retry: bool) -> Result<()>
Ok(())
}

/// Runs the provided Command object, captures its stdout, and swallows its stderr except on
/// failure. Returns a Result<String> describing whether the command failed, and if not, its
/// standard output. Output is assumed to be UTF-8. Errors are adequately prefixed with the full
/// command.
pub(crate) fn cmd_output(cmd: &mut Command) -> Result<String> {
let result = cmd
.output()
.with_context(|| format!("running {:#?}", cmd))?;
if !result.status.success() {
eprint!("{}", String::from_utf8_lossy(&result.stderr));
anyhow::bail!("{:#?} failed with {}", cmd, result.status);
}
String::from_utf8(result.stdout)
.with_context(|| format!("decoding as UTF-8 output of `{:#?}`", cmd))
}

/// Parse key-value pairs from lsblk --pairs.
/// Newer versions of lsblk support JSON but the one in CentOS 7 doesn't.
fn split_lsblk_line(line: &str) -> HashMap<String, String> {
static REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r#"([A-Z-_]+)="([^"]+)""#).unwrap());
let mut fields: HashMap<String, String> = HashMap::new();
for cap in REGEX.captures_iter(line) {
fields.insert(cap[1].to_string(), cap[2].to_string());
}
fields
}

/// This is a bit fuzzy, but... this function will return every block device in the parent
/// hierarchy of `device` capable of containing other partitions. So e.g. parent devices of type
/// "part" doesn't match, but "disk" and "mpath" does.
pub(crate) fn find_parent_devices(device: &str) -> Result<Vec<String>> {
let mut cmd = Command::new("lsblk");
// Older lsblk, e.g. in CentOS 7.6, doesn't support PATH, but --paths option
cmd.arg("--pairs")
.arg("--paths")
.arg("--inverse")
.arg("--output")
.arg("NAME,TYPE")
.arg(device);
let output = cmd_output(&mut cmd)?;
let mut parents = Vec::new();
// skip first line, which is the device itself
for line in output.lines().skip(1) {
let dev = split_lsblk_line(line);
let name = dev
.get("NAME")
.with_context(|| format!("device in hierarchy of {device} missing NAME"))?;
let kind = dev
.get("TYPE")
.with_context(|| format!("device in hierarchy of {device} missing TYPE"))?;
if kind == "disk" {
parents.push(name.clone());
} else if kind == "mpath" {
parents.push(name.clone());
// we don't need to know what disks back the multipath
break;
}
}
Ok(parents)
}

// create unsafe ioctl wrappers
#[allow(clippy::missing_safety_doc)]
mod ioctl {
Expand Down
4 changes: 3 additions & 1 deletion lib/src/bootloader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ pub(crate) const IGNITION_VARIABLE: &str = "$ignition_firstboot";
const GRUB_BOOT_UUID_FILE: &str = "bootuuid.cfg";
const STATIC_GRUB_CFG: &str = include_str!("grub.cfg");
const STATIC_GRUB_CFG_EFI: &str = include_str!("grub-efi.cfg");
/// The name of the mountpoint for efi (as a subdirectory of /boot, or at the toplevel)
pub(crate) const EFI_DIR: &str = "efi";

fn install_grub2_efi(efidir: &Dir, uuid: &str) -> Result<()> {
let mut vendordir = None;
Expand Down Expand Up @@ -64,7 +66,7 @@ pub(crate) fn install_via_bootupd(
let bootfs = &rootfs.join("boot");

{
let efidir = Dir::open_ambient_dir(&bootfs.join("efi"), cap_std::ambient_authority())?;
let efidir = Dir::open_ambient_dir(bootfs.join("efi"), cap_std::ambient_authority())?;
install_grub2_efi(&efidir, &grub2_uuid_contents)?;
}

Expand Down
12 changes: 12 additions & 0 deletions lib/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,13 @@ pub(crate) enum TestingOpts {
RunPrivilegedIntegration {},
/// Execute integration tests that target a not-privileged ostree container
RunContainerIntegration {},
/// Block device setup for testing
PrepTestInstallFilesystem { blockdev: Utf8PathBuf },
/// e2e test of install-to-filesystem
TestInstallFilesystem {
image: String,
blockdev: Utf8PathBuf,
},
}

/// Deploy and upgrade via bootable container images.
Expand All @@ -99,6 +106,9 @@ pub(crate) enum Opt {
/// Install to the target block device
#[cfg(feature = "install")]
Install(crate::install::InstallOpts),
/// Install to the target filesystem.
#[cfg(feature = "install")]
InstallToFilesystem(crate::install::InstallToFilesystemOpts),
/// Internal integration testing helpers.
#[clap(hide(true), subcommand)]
#[cfg(feature = "internal-testing-api")]
Expand Down Expand Up @@ -336,6 +346,8 @@ where
Opt::Switch(opts) => switch(opts).await,
#[cfg(feature = "install")]
Opt::Install(opts) => crate::install::install(opts).await,
#[cfg(feature = "install")]
Opt::InstallToFilesystem(opts) => crate::install::install_to_filesystem(opts).await,
Opt::Status(opts) => super::status::status(opts).await,
#[cfg(feature = "internal-testing-api")]
Opt::InternalTests(opts) => crate::privtests::run(opts).await,
Expand Down
2 changes: 1 addition & 1 deletion lib/src/ignition.rs
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@ mod tests {
(false, "sha512-cdaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f")
];
for (valid, hash_arg) in &hash_args {
let hasher = IgnitionHash::from_str(&hash_arg).unwrap();
let hasher = IgnitionHash::from_str(hash_arg).unwrap();
let mut rd = std::io::Cursor::new(&input);
assert!(hasher.validate(&mut rd).is_ok() == *valid);
}
Expand Down
Loading

0 comments on commit 8078700

Please sign in to comment.