Skip to content

Commit

Permalink
Add "rhsm" feature for integration with Red Hat Subscription Manager
Browse files Browse the repository at this point in the history
This adds a new subcommand `bootc internals publish-rhsm-facts` which
writes out facts data to /etc/rhsm/facts/bootc.json

Signed-off-by: John Eckersberg <[email protected]>
  • Loading branch information
jeckersb committed Dec 18, 2024
1 parent 63577cd commit d7e34fa
Show file tree
Hide file tree
Showing 4 changed files with 139 additions and 0 deletions.
3 changes: 3 additions & 0 deletions lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,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 internals publish-rhsm-facts` to integrate with
# Red Hat Subscription Manager
rhsm = []
# Implementation detail of man page generation.
docgen = ["clap_mangen"]

Expand Down
5 changes: 5 additions & 0 deletions lib/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,9 @@ pub(crate) enum InternalsOpts {
// The stateroot
stateroot: String,
},
#[cfg(feature = "rhsm")]
/// Publish subscription-manager facts to /etc/rhsm/facts/bootc.json
PublishRhsmFacts,
}

#[derive(Debug, clap::Subcommand, PartialEq, Eq)]
Expand Down Expand Up @@ -1094,6 +1097,8 @@ async fn run_from_opt(opt: Opt) -> Result<()> {
let rootfs = &Dir::open_ambient_dir("/", cap_std::ambient_authority())?;
crate::install::completion::run_from_ostree(rootfs, &sysroot, &stateroot).await
}
#[cfg(feature = "rhsm")]
InternalsOpts::PublishRhsmFacts => crate::rhsm::publish_facts(&root).await,
},
#[cfg(feature = "docgen")]
Opt::Man(manopts) => crate::docgen::generate_manpages(&manopts.directory),
Expand Down
3 changes: 3 additions & 0 deletions lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,6 @@ mod install;
mod kernel;
#[cfg(feature = "install")]
pub(crate) mod mount;

#[cfg(feature = "rhsm")]
mod rhsm;
128 changes: 128 additions & 0 deletions lib/src/rhsm.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
//! Integration with Red Hat Subscription Manager
use anyhow::Result;
use cap_std::fs::{Dir, OpenOptions};
use cap_std_ext::cap_std;
use fn_error_context::context;
use serde::Serialize;

const FACTS_PATH: &str = "etc/rhsm/facts/bootc.json";

#[derive(Serialize)]
struct RhsmFacts {
#[serde(rename = "bootc.booted.image")]
booted_image: String,
#[serde(rename = "bootc.booted.version")]
booted_version: String,
#[serde(rename = "bootc.booted.digest")]
booted_digest: String,
#[serde(rename = "bootc.staged.image")]
staged_image: String,
#[serde(rename = "bootc.staged.version")]
staged_version: String,
#[serde(rename = "bootc.staged.digest")]
staged_digest: String,
#[serde(rename = "bootc.rollback.image")]
rollback_image: String,
#[serde(rename = "bootc.rollback.version")]
rollback_version: String,
#[serde(rename = "bootc.rollback.digest")]
rollback_digest: String,
#[serde(rename = "bootc.available.image")]
available_image: String,
#[serde(rename = "bootc.available.version")]
available_version: String,
#[serde(rename = "bootc.available.digest")]
available_digest: String,
}

impl From<crate::spec::HostStatus> for RhsmFacts {
fn from(hoststatus: crate::spec::HostStatus) -> Self {
let (booted_image, booted_version, booted_digest) = hoststatus
.booted
.as_ref()
.and_then(|boot_entry| {
boot_entry.image.as_ref().map(|imagestatus| {
let image = imagestatus.image.image.clone();
let version = imagestatus.version.as_ref().cloned().unwrap_or_default();
let digest = imagestatus.image_digest.clone();

(image, digest, version)
})
})
.unwrap_or_default();

let (staged_image, staged_version, staged_digest) = hoststatus
.staged
.as_ref()
.and_then(|boot_entry| {
boot_entry.image.as_ref().map(|imagestatus| {
let image = imagestatus.image.image.clone();
let version = imagestatus.version.as_ref().cloned().unwrap_or_default();
let digest = imagestatus.image_digest.clone();

(image, digest, version)
})
})
.unwrap_or_default();

let (rollback_image, rollback_version, rollback_digest) = hoststatus
.rollback
.as_ref()
.and_then(|boot_entry| {
boot_entry.image.as_ref().map(|imagestatus| {
let image = imagestatus.image.image.clone();
let version = imagestatus.version.as_ref().cloned().unwrap_or_default();
let digest = imagestatus.image_digest.clone();

(image, digest, version)
})
})
.unwrap_or_default();

let (available_image, available_version, available_digest) = hoststatus
.booted
.as_ref()
.and_then(|boot_entry| {
boot_entry.cached_update.as_ref().map(|imagestatus| {
let image = imagestatus.image.image.clone();
let version = imagestatus.version.as_ref().cloned().unwrap_or_default();
let digest = imagestatus.image_digest.clone();

(image, digest, version)
})
})
.unwrap_or_default();

Self {
booted_image,
booted_version,
booted_digest,
staged_image,
staged_version,
staged_digest,
rollback_image,
rollback_version,
rollback_digest,
available_image,
available_version,
available_digest,
}
}
}

/// Publish facts for subscription-manager consumption
#[context("Publishing facts")]
pub(crate) async fn publish_facts(root: &Dir) -> Result<()> {
let sysroot = super::cli::get_storage().await?;
let booted_deployment = sysroot.booted_deployment();
let (_deployments, host) = crate::status::get_status(&sysroot, booted_deployment.as_ref())?;

let facts = RhsmFacts::from(host.status);
let mut bootc_facts_file = root.open_with(
FACTS_PATH,
OpenOptions::new().write(true).create(true).truncate(true),
)?;
serde_json::to_writer_pretty(&mut bootc_facts_file, &facts)?;
Ok(())
}

0 comments on commit d7e34fa

Please sign in to comment.