From 858acbe5ddfbf0693f260fbfce06f09ace2a5a18 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 an entrypoint that basically everyone can start adding to their builds which performs some basic static analysis for known problems. Closes : #216 Co-authored-by: Joseph Marrero Co-authored-by: Huijing Hei Co-authored-by: Yasmin de Souza Signed-off-by: Steven Presti Signed-off-by: Colin Walters --- docs/src/man/bootc-container-lint.md | 23 +++++++++ docs/src/man/bootc-container.md | 33 +++++++++++++ docs/src/man/bootc.md | 4 ++ hack/Containerfile | 1 + lib/src/cli.rs | 25 ++++++++++ lib/src/lib.rs | 1 + lib/src/lints.rs | 72 ++++++++++++++++++++++++++++ 7 files changed, 159 insertions(+) create mode 100644 docs/src/man/bootc-container-lint.md create mode 100644 docs/src/man/bootc-container.md create mode 100644 lib/src/lints.rs diff --git a/docs/src/man/bootc-container-lint.md b/docs/src/man/bootc-container-lint.md new file mode 100644 index 000000000..4badd8bf8 --- /dev/null +++ b/docs/src/man/bootc-container-lint.md @@ -0,0 +1,23 @@ +# NAME + +bootc-container-lint - Perform relatively inexpensive static analysis +checks as part of a container build + +# SYNOPSIS + +**bootc container lint** \[**-h**\|**\--help**\] + +# DESCRIPTION + +Perform relatively inexpensive static analysis checks as part of a +container build + +# OPTIONS + +**-h**, **\--help** + +: Print help + +# VERSION + +v0.1.11 diff --git a/docs/src/man/bootc-container.md b/docs/src/man/bootc-container.md new file mode 100644 index 000000000..5fdde1803 --- /dev/null +++ b/docs/src/man/bootc-container.md @@ -0,0 +1,33 @@ +# NAME + +bootc-container - Operations which can be executed as part of a +container build + +# SYNOPSIS + +**bootc container** \[**-h**\|**\--help**\] \<*subcommands*\> + +# DESCRIPTION + +Operations which can be executed as part of a container build + +# OPTIONS + +**-h**, **\--help** + +: Print help + +# SUBCOMMANDS + +bootc-container-lint(8) + +: Perform relatively inexpensive static analysis checks as part of a + container build + +bootc-container-help(8) + +: Print this message or the help of the given subcommand(s) + +# VERSION + +v0.1.11 diff --git a/docs/src/man/bootc.md b/docs/src/man/bootc.md index bbcdf66d7..22ba55348 100644 --- a/docs/src/man/bootc.md +++ b/docs/src/man/bootc.md @@ -62,6 +62,10 @@ bootc-install(8) : Install the running container to a target +bootc-container(8) + +: Operations which can be executed as part of a container build + bootc-help(8) : Print this message or the help of the given subcommand(s) diff --git a/hack/Containerfile b/hack/Containerfile index fd8c61159..e8e404294 100644 --- a/hack/Containerfile +++ b/hack/Containerfile @@ -16,3 +16,4 @@ FROM $base COPY --from=build /out/bootc.tar.zst /tmp COPY --from=build /build/target/dev-rootfs/ / RUN tar -C / --zstd -xvf /tmp/bootc.tar.zst && rm -vf /tmp/* +RUN bootc container lint diff --git a/lib/src/cli.rs b/lib/src/cli.rs index cbbfe3587..760332442 100644 --- a/lib/src/cli.rs +++ b/lib/src/cli.rs @@ -19,6 +19,7 @@ use std::os::unix::process::CommandExt; use std::process::Command; use crate::deploy::RequiredHostSpec; +use crate::lints; use crate::spec::Host; use crate::spec::ImageReference; use crate::utils::sigpolicy_from_opts; @@ -143,6 +144,14 @@ pub(crate) struct ManOpts { pub(crate) directory: Utf8PathBuf, } +/// Subcommands which can be executed as part of a container build. +#[derive(Debug, clap::Subcommand, PartialEq, Eq)] +pub(crate) enum ContainerOpts { + /// Perform relatively inexpensive static analysis checks as part of a container + /// build. + Lint, +} + /// Hidden, internal only options #[derive(Debug, clap::Subcommand, PartialEq, Eq)] pub(crate) enum InternalsOpts { @@ -292,6 +301,9 @@ pub(crate) enum Opt { #[clap(subcommand)] #[cfg(feature = "install")] Install(InstallOpts), + /// Operations which can be executed as part of a container build. + #[clap(subcommand)] + Container(ContainerOpts), /// Execute the given command in the host mount namespace #[cfg(feature = "install")] #[clap(hide = true)] @@ -662,6 +674,19 @@ 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) => match opts { + ContainerOpts::Lint => { + 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())?; + lints::lint(&root)?; + Ok(()) + } + }, #[cfg(feature = "install")] Opt::Install(opts) => match opts { InstallOpts::ToDisk(opts) => crate::install::install_to_disk(opts).await, diff --git a/lib/src/lib.rs b/lib/src/lib.rs index b04d33642..5a18a7de4 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -21,6 +21,7 @@ pub mod cli; pub(crate) mod deploy; pub(crate) mod generator; pub(crate) mod journal; +mod lints; mod lsm; pub(crate) mod metadata; mod reboot; diff --git a/lib/src/lints.rs b/lib/src/lints.rs new file mode 100644 index 000000000..977208009 --- /dev/null +++ b/lib/src/lints.rs @@ -0,0 +1,72 @@ +//! # Implementation of container build lints +//! +//! This module implements `bootc container lint`. + +use anyhow::Result; +use cap_std::fs::Dir; +use cap_std_ext::cap_std; +use cap_std_ext::dirext::CapStdExtDirExt as _; +use fn_error_context::context; + +/// check for the existence of the /var/run directory +/// if it exists we need to check that it links to /run if not error +/// if it does not exist error. +#[context("Linting")] +pub(crate) fn lint(root: &Dir) -> Result<()> { + let lints = [check_var_run, check_kernel]; + for lint in lints { + lint(&root)?; + } + println!("Checks passed: {}", lints.len()); + Ok(()) +} + +fn check_var_run(root: &Dir) -> Result<()> { + if let Some(meta) = root.symlink_metadata_optional("var/run")? { + if !meta.is_symlink() { + anyhow::bail!("Not a symlink: var/run"); + } + } + Ok(()) +} + +fn check_kernel(root: &Dir) -> Result<()> { + let result = ostree_ext::bootabletree::find_kernel_dir_fs(&root)?; + tracing::debug!("Found kernel: {:?}", result); + Ok(()) +} + +#[cfg(test)] +fn fixture() -> Result { + let tempdir = cap_std_ext::cap_tempfile::tempdir(cap_std::ambient_authority())?; + Ok(tempdir) +} + +#[test] +fn test_var_run() -> Result<()> { + let root = &fixture()?; + // This one should pass + check_var_run(root).unwrap(); + root.create_dir_all("var/run/foo")?; + assert!(check_var_run(root).is_err()); + root.remove_dir_all("var/run")?; + // Now we should pass again + check_var_run(root).unwrap(); + Ok(()) +} + +#[test] +fn test_kernel_lint() -> Result<()> { + let root = &fixture()?; + // This one should pass + check_kernel(root).unwrap(); + root.create_dir_all("usr/lib/modules/5.7.2")?; + root.write("usr/lib/modules/5.7.2/vmlinuz", "old vmlinuz")?; + root.create_dir_all("usr/lib/modules/6.3.1")?; + root.write("usr/lib/modules/6.3.1/vmlinuz", "new vmlinuz")?; + assert!(check_kernel(root).is_err()); + root.remove_dir_all("usr/lib/modules/5.7.2")?; + // Now we should pass again + check_kernel(root).unwrap(); + Ok(()) +}