Skip to content

Commit 0b09728

Browse files
committed
Add "rhsm" feature for integration with Red Hat Subscription Manager
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]>
1 parent caecb64 commit 0b09728

File tree

4 files changed

+168
-0
lines changed

4 files changed

+168
-0
lines changed

lib/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ static_assertions = { workspace = true }
5656
default = ["install"]
5757
# This feature enables `bootc install`. Disable if you always want to use an external installer.
5858
install = []
59+
# This featuares enables `bootc internals publish-rhsm-facts` to integrate with
60+
# Red Hat Subscription Manager
61+
rhsm = []
5962
# Implementation detail of man page generation.
6063
docgen = ["clap_mangen"]
6164

lib/src/cli.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,9 @@ pub(crate) enum InternalsOpts {
393393
// The stateroot
394394
stateroot: String,
395395
},
396+
#[cfg(feature = "rhsm")]
397+
/// Publish subscription-manager facts to /etc/rhsm/facts/bootc.json
398+
PublishRhsmFacts,
396399
}
397400

398401
#[derive(Debug, clap::Subcommand, PartialEq, Eq)]
@@ -1106,6 +1109,8 @@ async fn run_from_opt(opt: Opt) -> Result<()> {
11061109
let rootfs = &Dir::open_ambient_dir("/", cap_std::ambient_authority())?;
11071110
crate::install::completion::run_from_ostree(rootfs, &sysroot, &stateroot).await
11081111
}
1112+
#[cfg(feature = "rhsm")]
1113+
InternalsOpts::PublishRhsmFacts => crate::rhsm::publish_facts(&root).await,
11091114
},
11101115
#[cfg(feature = "docgen")]
11111116
Opt::Man(manopts) => crate::docgen::generate_manpages(&manopts.directory),

lib/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,6 @@ mod install;
4242
mod kernel;
4343
#[cfg(feature = "install")]
4444
pub(crate) mod mount;
45+
46+
#[cfg(feature = "rhsm")]
47+
mod rhsm;

lib/src/rhsm.rs

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
//! Integration with Red Hat Subscription Manager
2+
3+
use anyhow::Result;
4+
use cap_std::fs::{Dir, OpenOptions};
5+
use cap_std_ext::cap_std;
6+
use fn_error_context::context;
7+
use serde::Serialize;
8+
9+
const FACTS_PATH: &str = "etc/rhsm/facts/bootc.json";
10+
11+
#[derive(Serialize, PartialEq, Eq, Debug, Default)]
12+
struct RhsmFacts {
13+
#[serde(rename = "bootc.booted.image")]
14+
booted_image: String,
15+
#[serde(rename = "bootc.booted.version")]
16+
booted_version: String,
17+
#[serde(rename = "bootc.booted.digest")]
18+
booted_digest: String,
19+
#[serde(rename = "bootc.staged.image")]
20+
staged_image: String,
21+
#[serde(rename = "bootc.staged.version")]
22+
staged_version: String,
23+
#[serde(rename = "bootc.staged.digest")]
24+
staged_digest: String,
25+
#[serde(rename = "bootc.rollback.image")]
26+
rollback_image: String,
27+
#[serde(rename = "bootc.rollback.version")]
28+
rollback_version: String,
29+
#[serde(rename = "bootc.rollback.digest")]
30+
rollback_digest: String,
31+
#[serde(rename = "bootc.available.image")]
32+
available_image: String,
33+
#[serde(rename = "bootc.available.version")]
34+
available_version: String,
35+
#[serde(rename = "bootc.available.digest")]
36+
available_digest: String,
37+
}
38+
39+
impl From<crate::spec::HostStatus> for RhsmFacts {
40+
fn from(hoststatus: crate::spec::HostStatus) -> Self {
41+
let (booted_image, booted_version, booted_digest) = hoststatus
42+
.booted
43+
.as_ref()
44+
.and_then(|boot_entry| {
45+
boot_entry.image.as_ref().map(|imagestatus| {
46+
let image = imagestatus.image.image.clone();
47+
let version = imagestatus.version.as_ref().cloned().unwrap_or_default();
48+
let digest = imagestatus.image_digest.clone();
49+
50+
(image, version, digest)
51+
})
52+
})
53+
.unwrap_or_default();
54+
55+
let (staged_image, staged_version, staged_digest) = hoststatus
56+
.staged
57+
.as_ref()
58+
.and_then(|boot_entry| {
59+
boot_entry.image.as_ref().map(|imagestatus| {
60+
let image = imagestatus.image.image.clone();
61+
let version = imagestatus.version.as_ref().cloned().unwrap_or_default();
62+
let digest = imagestatus.image_digest.clone();
63+
64+
(image, version, digest)
65+
})
66+
})
67+
.unwrap_or_default();
68+
69+
let (rollback_image, rollback_version, rollback_digest) = hoststatus
70+
.rollback
71+
.as_ref()
72+
.and_then(|boot_entry| {
73+
boot_entry.image.as_ref().map(|imagestatus| {
74+
let image = imagestatus.image.image.clone();
75+
let version = imagestatus.version.as_ref().cloned().unwrap_or_default();
76+
let digest = imagestatus.image_digest.clone();
77+
78+
(image, version, digest)
79+
})
80+
})
81+
.unwrap_or_default();
82+
83+
let (available_image, available_version, available_digest) = hoststatus
84+
.booted
85+
.as_ref()
86+
.and_then(|boot_entry| {
87+
boot_entry.cached_update.as_ref().map(|imagestatus| {
88+
let image = imagestatus.image.image.clone();
89+
let version = imagestatus.version.as_ref().cloned().unwrap_or_default();
90+
let digest = imagestatus.image_digest.clone();
91+
92+
(image, version, digest)
93+
})
94+
})
95+
.unwrap_or_default();
96+
97+
Self {
98+
booted_image,
99+
booted_version,
100+
booted_digest,
101+
staged_image,
102+
staged_version,
103+
staged_digest,
104+
rollback_image,
105+
rollback_version,
106+
rollback_digest,
107+
available_image,
108+
available_version,
109+
available_digest,
110+
}
111+
}
112+
}
113+
114+
/// Publish facts for subscription-manager consumption
115+
#[context("Publishing facts")]
116+
pub(crate) async fn publish_facts(root: &Dir) -> Result<()> {
117+
let sysroot = super::cli::get_storage().await?;
118+
let booted_deployment = sysroot.booted_deployment();
119+
let (_deployments, host) = crate::status::get_status(&sysroot, booted_deployment.as_ref())?;
120+
121+
let facts = RhsmFacts::from(host.status);
122+
let mut bootc_facts_file = root.open_with(
123+
FACTS_PATH,
124+
OpenOptions::new().write(true).create(true).truncate(true),
125+
)?;
126+
serde_json::to_writer_pretty(&mut bootc_facts_file, &facts)?;
127+
Ok(())
128+
}
129+
130+
#[cfg(test)]
131+
mod tests {
132+
use super::*;
133+
134+
use crate::spec::Host;
135+
136+
#[test]
137+
fn test_rhsm_facts_from_host() {
138+
let host: Host = serde_yaml::from_str(include_str!("fixtures/spec-staged-booted.yaml"))
139+
.expect("No spec found");
140+
let facts = RhsmFacts::from(host.status);
141+
142+
assert_eq!(
143+
facts,
144+
RhsmFacts {
145+
booted_image: "quay.io/example/someimage:latest".into(),
146+
booted_version: "nightly".into(),
147+
booted_digest:
148+
"sha256:736b359467c9437c1ac915acaae952aad854e07eb4a16a94999a48af08c83c34".into(),
149+
staged_image: "quay.io/example/someimage:latest".into(),
150+
staged_version: "nightly".into(),
151+
staged_digest:
152+
"sha256:16dc2b6256b4ff0d2ec18d2dbfb06d117904010c8cf9732cdb022818cf7a7566".into(),
153+
..Default::default()
154+
}
155+
);
156+
}
157+
}

0 commit comments

Comments
 (0)