From a0de33de8d84d74c8ec617eadc5323e6d18846cb Mon Sep 17 00:00:00 2001 From: Steven Presti Date: Thu, 21 Mar 2024 10:37:04 -0400 Subject: [PATCH] cli: add container --lint Add initial lint functionality, the first lint checks if there are multiple kernels in the image. fixes: #216 Co-authored-by: Joseph Marrero Co-authored-by: Huijing Hei Co-authored-by: Yasmin de Souza Co-authored-by: Renata Ravanelli Co-authored-by: Adam Piasecki --- lib/src/cli.rs | 55 ++++++++++++++++++++++++++++++++++++++++++++ lib/src/privtests.rs | 38 +++++++++++++++++++++++++++++- 2 files changed, 92 insertions(+), 1 deletion(-) diff --git a/lib/src/cli.rs b/lib/src/cli.rs index 05f1b2b7..02f3fbe8 100644 --- a/lib/src/cli.rs +++ b/lib/src/cli.rs @@ -143,6 +143,13 @@ pub(crate) struct ManOpts { pub(crate) directory: Utf8PathBuf, } +#[derive(Debug, Parser, PartialEq, Eq)] +pub(crate) struct ContainerOpts { + /// Run linting checking for files not supported on the container. + #[clap(long)] + pub(crate) lint: bool, +} + /// Hidden, internal only options #[derive(Debug, clap::Subcommand, PartialEq, Eq)] pub(crate) enum InternalsOpts { @@ -181,6 +188,10 @@ pub(crate) enum TestingOpts { #[clap(long)] warn: bool, }, + // Test set of lints on ostree container + TestBuildLint { + image: String, + }, } /// Deploy and transactionally in-place with bootable container images. @@ -292,6 +303,8 @@ pub(crate) enum Opt { #[clap(subcommand)] #[cfg(feature = "install")] Install(InstallOpts), + /// Commands to run on the container like lint. + Container(ContainerOpts), /// Execute the given command in the host mount namespace #[cfg(feature = "install")] #[clap(hide = true)] @@ -600,6 +613,14 @@ async fn edit(opts: EditOpts) -> Result<()> { Ok(()) } +async fn container(opts: ContainerOpts) -> Result<()> { + if opts.lint { + lint()?; + } + + Ok(()) +} + /// Implementation of `bootc usroverlay` async fn usroverlay() -> Result<()> { // This is just a pass-through today. At some point we may make this a libostree API @@ -610,6 +631,21 @@ async fn usroverlay() -> Result<()> { .into()); } +/// Implementation of `bootc build commit` +/// async fn lint() -> Result<()> { +#[context("linting")] +fn lint() -> Result<()> { + if !ostree_ext::container_utils::is_ostree_container()? { + anyhow::bail!( + "Not in a ostree container, this command only verifies ostree containers." + ); + } + + let root = cap_std::fs::Dir::open_ambient_dir("/", cap_std::ambient_authority())?; + tracing::debug!("Found kernel: {:?}", ostree_ext::bootabletree::find_kernel_dir_fs(&root)?); + return Ok(()); +} + /// Parse the provided arguments and execute. /// Calls [`structopt::clap::Error::exit`] on failure, printing the error message and aborting the program. pub async fn run_from_iter(args: I) -> Result<()> @@ -656,6 +692,7 @@ async fn run_from_opt(opt: Opt) -> Result<()> { Opt::Rollback(opts) => rollback(opts).await, Opt::Edit(opts) => edit(opts).await, Opt::UsrOverlay => usroverlay().await, + Opt::Container(opts) => container(opts).await, #[cfg(feature = "install")] Opt::Install(opts) => match opts { InstallOpts::ToDisk(opts) => crate::install::install_to_disk(opts).await, @@ -730,3 +767,21 @@ fn test_parse_generator() { Opt::Internals(InternalsOpts::SystemdGenerator { .. }) )); } + +#[test] +fn test_linting() { + // linting should only occur in side of a container. + match ostree_ext::container_utils::is_ostree_container() { + Ok(result) => { + if !result { + let expected_error_message = "Not in a ostree container, this command only verifies ostree containers."; + + let result = lint(); + assert_eq!(result.err().unwrap().to_string(), expected_error_message, "Error message mismatch"); + } + + }, + Err(_) =>{ + } + } +} diff --git a/lib/src/privtests.rs b/lib/src/privtests.rs index ea56c077..8783de00 100644 --- a/lib/src/privtests.rs +++ b/lib/src/privtests.rs @@ -9,7 +9,6 @@ use cap_std_ext::cap_std::fs::Dir; use fn_error_context::context; use rustix::fd::AsFd; use xshell::{cmd, Shell}; - use crate::blockdev::LoopbackDevice; use crate::install::config::InstallConfiguration; @@ -196,6 +195,37 @@ fn verify_selinux_recurse(root: &Dir, path: &mut PathBuf, warn: bool) -> Result< Ok(()) } +#[context("Container tests")] +fn test_container_lint(image: &str) -> Result<()> { + + let sh = Shell::new()?; + + // Smoke test of container --lint + let _test_1_result = cmd!(sh, "podman run --rm --privileged --pid=host --env=RUST_LOG -v /usr/bin/bootc:/usr/bin/bootc {image} bootc container --lint").run(); + + // Setup for multiple kernels lint test + cmd!(sh, "podman run -dt --name test --privileged --pid=host --env=RUST_LOG -v /usr/bin/bootc:/usr/bin/bootc {image} bash").run()?; + let kernel_name = cmd!(sh, "podman exec test bash -c 'ls /usr/lib/modules | tail -n -1'" ).read()?; + Command::new("podman") + .arg("exec") + .arg("test") + .arg("bash") + .arg("-c") + .arg(format!("sudo cp -r /usr/lib/modules/{} /usr/lib/modules/delete-me", kernel_name)) + .output()?; + let more_then_one_kernel_result = cmd!(sh, "podman exec test bash -c 'bootc container --lint'").read_stderr(); + // Container Cleanup + cmd!(sh, "podman rm -f test").run()?; + + _test_1_result?; + if let Err(e) = more_then_one_kernel_result { + assert!(e.to_string().contains("bootc container --lint")); + } else { + assert!(false, "Expected error, got none"); + } + Ok(()) +} + pub(crate) async fn run(opts: TestingOpts) -> Result<()> { match opts { TestingOpts::RunPrivilegedIntegration {} => { @@ -221,5 +251,11 @@ pub(crate) async fn run(opts: TestingOpts) -> Result<()> { tokio::task::spawn_blocking(move || verify_selinux_recurse(&rootfs, &mut path, warn)) .await? } + TestingOpts::TestBuildLint { image } => { + tokio::task::spawn_blocking(move || test_build_lint(&image)).await? + } + TestingOpts::TestContainerLint { image } => { + tokio::task::spawn_blocking(move || test_container_lint(&image)).await? + } } }