From 93afefeeb7ad55cac3b42e10f994be2217539b53 Mon Sep 17 00:00:00 2001 From: John Eckersberg Date: Mon, 9 Dec 2024 11:12:16 -0500 Subject: [PATCH] WIP rhsm --- Makefile | 2 +- lib/Cargo.toml | 3 +++ lib/src/cli.rs | 6 ++++++ lib/src/deploy.rs | 3 +++ lib/src/imgstorage.rs | 24 +++++++++++++-------- lib/src/store/mod.rs | 27 ++++++++++++++++++++++-- systemd/bootc-rhsm-facts-publish.service | 11 ++++++++++ systemd/bootc-status-updated.path | 10 +++++++++ systemd/bootc-status-updated.target | 4 ++++ 9 files changed, 78 insertions(+), 12 deletions(-) create mode 100644 systemd/bootc-rhsm-facts-publish.service create mode 100644 systemd/bootc-status-updated.path create mode 100644 systemd/bootc-status-updated.target diff --git a/Makefile b/Makefile index 41a6454c6..39df308b7 100644 --- a/Makefile +++ b/Makefile @@ -24,7 +24,7 @@ install: install -D -m 0644 -t $(DESTDIR)$(prefix)/share/man/man8 $$d/*.8; \ fi; \ done - install -D -m 0644 -t $(DESTDIR)/$(prefix)/lib/systemd/system systemd/*.service systemd/*.timer + install -D -m 0644 -t $(DESTDIR)/$(prefix)/lib/systemd/system systemd/*.service systemd/*.timer systemd/*.path systemd/*.target # Run this to also take over the functionality of `ostree container` for example. # Only needed for OS/distros that have callers invoking `ostree container` and not bootc. diff --git a/lib/Cargo.toml b/lib/Cargo.toml index 1a89154c8..1f8fff64e 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -57,6 +57,9 @@ static_assertions = { workspace = true } default = ["install"] # This feature enables `bootc install`. Disable if you always want to use an external installer. install = [] +# This featuares enables `bootc rhsm-facts-publish` to integrate with +# Red Hat Subscription Manager +rhsm = [] # Implementation detail of man page generation. docgen = ["clap_mangen"] diff --git a/lib/src/cli.rs b/lib/src/cli.rs index d1cf0d9ce..791e743d3 100644 --- a/lib/src/cli.rs +++ b/lib/src/cli.rs @@ -724,6 +724,8 @@ async fn upgrade(opts: UpgradeOpts) -> Result<()> { } } if changed { + sysroot.update_mtime()?; + if opts.apply { crate::reboot::reboot()?; } @@ -799,6 +801,8 @@ async fn switch(opts: SwitchOpts) -> Result<()> { let stateroot = booted_deployment.osname(); crate::deploy::stage(sysroot, &stateroot, &fetched, &new_spec).await?; + sysroot.update_mtime()?; + if opts.apply { crate::reboot::reboot()?; } @@ -852,6 +856,8 @@ async fn edit(opts: EditOpts) -> Result<()> { let stateroot = booted_deployment.osname(); crate::deploy::stage(sysroot, &stateroot, &fetched, &new_spec).await?; + sysroot.update_mtime()?; + Ok(()) } diff --git a/lib/src/deploy.rs b/lib/src/deploy.rs index 960c1abde..95a987275 100644 --- a/lib/src/deploy.rs +++ b/lib/src/deploy.rs @@ -531,6 +531,9 @@ pub(crate) async fn rollback(sysroot: &Storage) -> Result<()> { } else { println!("Next boot: rollback deployment"); } + + sysroot.update_mtime()?; + Ok(()) } diff --git a/lib/src/imgstorage.rs b/lib/src/imgstorage.rs index 93ec0f144..597c5b16b 100644 --- a/lib/src/imgstorage.rs +++ b/lib/src/imgstorage.rs @@ -13,7 +13,7 @@ use std::sync::Arc; use anyhow::{Context, Result}; use bootc_utils::{AsyncCommandRunExt, CommandRunExt, ExitStatusExt}; -use camino::Utf8Path; +use camino::Utf8PathBuf; use cap_std_ext::cap_std; use cap_std_ext::cap_std::fs::Dir; use cap_std_ext::cap_tempfile::TempDir; @@ -35,8 +35,8 @@ pub(crate) const STORAGE_ALIAS_DIR: &str = "/run/bootc/storage"; /// We pass this via /proc/self/fd to the child process. const STORAGE_RUN_FD: i32 = 3; -/// The path to the storage, relative to the physical system root. -pub(crate) const SUBPATH: &str = "ostree/bootc/storage"; +/// The path to the image storage, relative to the bootc root directory. +pub(crate) const SUBPATH: &str = "storage"; /// The path to the "runroot" with transient runtime state; this is /// relative to the /run directory const RUNROOT: &str = "bootc/storage"; @@ -139,14 +139,15 @@ impl Storage { #[context("Creating imgstorage")] pub(crate) fn create(sysroot: &Dir, run: &Dir) -> Result { Self::init_globals()?; - let subpath = Utf8Path::new(SUBPATH); + let subpath = Self::subpath(); + // SAFETY: We know there's a parent let parent = subpath.parent().unwrap(); if !sysroot - .try_exists(subpath) + .try_exists(&subpath) .with_context(|| format!("Querying {subpath}"))? { - let tmp = format!("{SUBPATH}.tmp"); + let tmp = format!("{subpath}.tmp"); sysroot.remove_all_optional(&tmp).context("Removing tmp")?; sysroot .create_dir_all(parent) @@ -163,7 +164,7 @@ impl Storage { .context("Initializing images")?; drop(storage_root); sysroot - .rename(&tmp, sysroot, subpath) + .rename(&tmp, sysroot, &subpath) .context("Renaming tmpdir")?; tracing::debug!("Created image store"); } @@ -174,9 +175,10 @@ impl Storage { pub(crate) fn open(sysroot: &Dir, run: &Dir) -> Result { tracing::trace!("Opening container image store"); Self::init_globals()?; + let subpath = Self::subpath(); let storage_root = sysroot - .open_dir(SUBPATH) - .with_context(|| format!("Opening {SUBPATH}"))?; + .open_dir(&subpath) + .with_context(|| format!("Opening {subpath}"))?; // Always auto-create this if missing run.create_dir_all(RUNROOT) .with_context(|| format!("Creating {RUNROOT}"))?; @@ -303,6 +305,10 @@ impl Storage { temp_runroot.close()?; Ok(()) } + + fn subpath() -> Utf8PathBuf { + [crate::store::BOOTC_ROOT, SUBPATH].iter().collect() + } } #[cfg(test)] diff --git a/lib/src/store/mod.rs b/lib/src/store/mod.rs index 56e142e8b..cbc5db80c 100644 --- a/lib/src/store/mod.rs +++ b/lib/src/store/mod.rs @@ -2,9 +2,10 @@ use std::cell::OnceCell; use std::env; use std::ops::Deref; -use anyhow::Result; -use cap_std_ext::cap_std::fs::Dir; +use anyhow::{Context, Result}; +use cap_std_ext::{cap_primitives, cap_std::fs::Dir}; use clap::ValueEnum; +use fn_error_context::context; use ostree_ext::container::OstreeImageReference; use ostree_ext::keyfileext::KeyFileExt; @@ -15,6 +16,10 @@ use crate::spec::ImageStatus; mod ostree_container; +/// The path to the bootc root directory, relative to the physical +/// system root +pub(crate) const BOOTC_ROOT: &str = "ostree/bootc"; + pub(crate) struct Storage { pub sysroot: SysrootLock, run: Dir, @@ -82,6 +87,24 @@ impl Storage { let imgstore = crate::imgstorage::Storage::create(&sysroot_dir, &self.run)?; Ok(self.imgstore.get_or_init(|| imgstore)) } + + /// Update the mtime on the storage root directory + /// This triggers the bootc-status-updated.path systemd unit + #[context("Updating storage root mtime")] + pub(crate) fn update_mtime(&self) -> Result<()> { + let sysroot_dir = Dir::reopen_dir(&crate::utils::sysroot_fd(&self.sysroot)) + .context("Reopen sysroot directory")? + .into_std_file(); + + cap_primitives::fs::set_times( + &sysroot_dir, + std::path::Path::new(BOOTC_ROOT), + Some(cap_primitives::fs::SystemTimeSpec::SymbolicNow), + Some(cap_primitives::fs::SystemTimeSpec::SymbolicNow), + ) + .context("set_times") + .map_err(Into::into) + } } impl ContainerImageStore for ostree::Deployment { diff --git a/systemd/bootc-rhsm-facts-publish.service b/systemd/bootc-rhsm-facts-publish.service new file mode 100644 index 000000000..3415b71a6 --- /dev/null +++ b/systemd/bootc-rhsm-facts-publish.service @@ -0,0 +1,11 @@ +[Unit] +Description=Publish bootc facts to Red Hat Subscription Manager +Documentation=man:bootc(8) +ConditionPathExists=/etc/rhsm + +[Service] +Type=oneshot +ExecStart=/usr/bin/bootc rhsm-facts-publish + +[Install] +WantedBy=bootc-status-modified.target diff --git a/systemd/bootc-status-updated.path b/systemd/bootc-status-updated.path new file mode 100644 index 000000000..da77b8979 --- /dev/null +++ b/systemd/bootc-status-updated.path @@ -0,0 +1,10 @@ +[Unit] +Description=Monitor bootc for status changes +Documentation=man:bootc(8) + +[Path] +PathChanged=/ostree/bootc +Unit=bootc-status-updated.target + +[Install] +WantedBy=multi-user.target diff --git a/systemd/bootc-status-updated.target b/systemd/bootc-status-updated.target new file mode 100644 index 000000000..1fa2d3885 --- /dev/null +++ b/systemd/bootc-status-updated.target @@ -0,0 +1,4 @@ +[Unit] +Description=Target for bootc status changes +Documentation=man:bootc(8) +StopWhenUnneeded=true