Skip to content

Commit 9789bbd

Browse files
Johan-Liebert1cgwalters
authored andcommitted
composefs-backend/update: Handle already staged update
Handle the case when an update is already staged. Handle the case when the staged image is not the same as the update image. Implement `--check` option Signed-off-by: Pragyan Poudyal <[email protected]>
1 parent 420c7c7 commit 9789bbd

File tree

2 files changed

+137
-8
lines changed

2 files changed

+137
-8
lines changed

crates/lib/src/bootc_composefs/status.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ pub(crate) fn get_sorted_type1_boot_entries(
131131

132132
/// imgref = transport:image_name
133133
#[context("Getting container info")]
134-
async fn get_container_manifest_and_config(
134+
pub(crate) async fn get_container_manifest_and_config(
135135
imgref: &String,
136136
) -> Result<(ImageManifest, oci_spec::image::ImageConfiguration)> {
137137
let config = containers_image_proxy::ImageProxyConfig::default();

crates/lib/src/bootc_composefs/update.rs

Lines changed: 136 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,158 @@
11
use anyhow::{Context, Result};
22
use camino::Utf8PathBuf;
3+
use composefs::util::{parse_sha256, Sha256Digest};
34
use fn_error_context::context;
5+
use ostree_ext::oci_spec::image::{ImageConfiguration, ImageManifest};
46

57
use crate::{
68
bootc_composefs::{
79
boot::{setup_composefs_bls_boot, setup_composefs_uki_boot, BootSetupType, BootType},
8-
repo::pull_composefs_repo,
10+
repo::{get_imgref, open_composefs_repo, pull_composefs_repo},
911
service::start_finalize_stated_svc,
1012
state::write_composefs_state,
11-
status::composefs_deployment_status,
13+
status::{composefs_deployment_status, get_container_manifest_and_config},
1214
},
1315
cli::UpgradeOpts,
16+
spec::ImageReference,
17+
store::ComposefsRepository,
1418
};
1519

20+
use cap_std_ext::cap_std::{ambient_authority, fs::Dir};
21+
22+
#[context("Getting SHA256 Digest for {id}")]
23+
pub fn str_to_sha256digest(id: &str) -> Result<Sha256Digest> {
24+
let id = if id.starts_with("sha256:") {
25+
id.strip_prefix("sha256:").unwrap()
26+
} else {
27+
id
28+
};
29+
30+
Ok(parse_sha256(&id)?)
31+
}
32+
33+
/// Checks if a container image has been pulled to the local composefs repository.
34+
///
35+
/// This function verifies whether the specified container image exists in the local
36+
/// composefs repository by checking if the image's configuration digest stream is
37+
/// available. It retrieves the image manifest and configuration from the container
38+
/// registry and uses the configuration digest to perform the local availability check.
39+
///
40+
/// # Arguments
41+
///
42+
/// * `repo` - The composefs repository
43+
/// * `imgref` - Reference to the container image to check
44+
///
45+
/// # Returns
46+
///
47+
/// Returns a tuple containing:
48+
/// * `true` if the image is pulled/available locally, `false` otherwise
49+
/// * The container image manifest
50+
/// * The container image configuration
51+
#[context("Checking if image {} is pulled", imgref.image)]
52+
async fn is_image_pulled(
53+
repo: &ComposefsRepository,
54+
imgref: &ImageReference,
55+
) -> Result<(bool, ImageManifest, ImageConfiguration)> {
56+
let imgref_repr = get_imgref(&imgref.transport, &imgref.image);
57+
let (manifest, config) = get_container_manifest_and_config(&imgref_repr).await?;
58+
59+
let img_digest = manifest.config().digest().digest();
60+
let img_sha256 = str_to_sha256digest(&img_digest)?;
61+
62+
// check_stream is expensive to run, but probably a good idea
63+
let container_pulled = repo.check_stream(&img_sha256).context("Checking stream")?;
64+
65+
Ok((container_pulled.is_some(), manifest, config))
66+
}
67+
1668
#[context("Upgrading composefs")]
17-
pub(crate) async fn upgrade_composefs(_opts: UpgradeOpts) -> Result<()> {
69+
pub(crate) async fn upgrade_composefs(opts: UpgradeOpts) -> Result<()> {
1870
let host = composefs_deployment_status()
1971
.await
2072
.context("Getting composefs deployment status")?;
2173

22-
start_finalize_stated_svc()?;
23-
24-
// TODO: IMPORTANT We need to check if any deployment is staged and get the image from that
25-
let imgref = host
74+
let mut imgref = host
2675
.spec
2776
.image
2877
.as_ref()
2978
.ok_or_else(|| anyhow::anyhow!("No image source specified"))?;
3079

80+
let sysroot =
81+
Dir::open_ambient_dir("/sysroot", ambient_authority()).context("Opening sysroot")?;
82+
let repo = open_composefs_repo(&sysroot)?;
83+
84+
let (img_pulled, mut manifest, mut config) = is_image_pulled(&repo, imgref).await?;
85+
let booted_img_digest = manifest.config().digest().digest();
86+
87+
// We already have this container config. No update available
88+
if img_pulled {
89+
println!("No changes in: {imgref:#}");
90+
// TODO(Johan-Liebert1): What if we have the config but we failed the previous update in the middle?
91+
return Ok(());
92+
}
93+
94+
// Check if we already have this update staged
95+
let staged_image = host.status.staged.as_ref().and_then(|i| i.image.as_ref());
96+
97+
if let Some(staged_image) = staged_image {
98+
// We have a staged image and it has the same digest as the currently booted image's latest
99+
// digest
100+
if staged_image.image_digest == booted_img_digest {
101+
if opts.apply {
102+
return crate::reboot::reboot();
103+
}
104+
105+
println!("Update already staged. To apply update run `bootc update --apply`");
106+
107+
return Ok(());
108+
}
109+
110+
// We have a staged image but it's not the update image.
111+
// Maybe it's something we got by `bootc switch`
112+
// Switch takes precedence over update, so we change the imgref
113+
imgref = &staged_image.image;
114+
115+
let (img_pulled, staged_manifest, staged_cfg) = is_image_pulled(&repo, imgref).await?;
116+
manifest = staged_manifest;
117+
config = staged_cfg;
118+
119+
// We already have this container config. No update available
120+
if img_pulled {
121+
println!("No changes in staged image: {imgref:#}");
122+
return Ok(());
123+
}
124+
}
125+
126+
if opts.check {
127+
// TODO(Johan-Liebert1): If we have the previous, i.e. the current manifest with us then we can replace the
128+
// following with [`ostree_container::ManifestDiff::new`] which will be much cleaner
129+
for (idx, diff_id) in config.rootfs().diff_ids().iter().enumerate() {
130+
let diff_id = str_to_sha256digest(diff_id)?;
131+
132+
// we could use `check_stream` here but that will most probably take forever as it
133+
// usually takes ~3s to verify one single layer
134+
let have_layer = repo.has_stream(&diff_id)?;
135+
136+
if have_layer.is_none() {
137+
if idx >= manifest.layers().len() {
138+
anyhow::bail!("Length mismatch between rootfs diff layers and manifest layers");
139+
}
140+
141+
let layer = &manifest.layers()[idx];
142+
143+
println!(
144+
"Added layer: {}\tSize: {}",
145+
layer.digest(),
146+
layer.size().to_string()
147+
);
148+
}
149+
}
150+
151+
return Ok(());
152+
}
153+
154+
start_finalize_stated_svc()?;
155+
31156
let (repo, entries, id, fs) = pull_composefs_repo(&imgref.transport, &imgref.image).await?;
32157

33158
let Some(entry) = entries.iter().next() else {
@@ -61,5 +186,9 @@ pub(crate) async fn upgrade_composefs(_opts: UpgradeOpts) -> Result<()> {
61186
boot_digest,
62187
)?;
63188

189+
if opts.apply {
190+
return crate::reboot::reboot();
191+
}
192+
64193
Ok(())
65194
}

0 commit comments

Comments
 (0)