Skip to content

Commit b9ecaac

Browse files
composefs: Ensure idempotency for switch
Similar to how we handle bootc update Signed-off-by: Pragyan Poudyal <pragyanpoudyal41999@gmail.com>
1 parent dee046d commit b9ecaac

File tree

2 files changed

+96
-65
lines changed

2 files changed

+96
-65
lines changed
Lines changed: 29 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
11
use anyhow::{Context, Result};
2-
use camino::Utf8PathBuf;
32
use fn_error_context::context;
43

54
use crate::{
65
bootc_composefs::{
7-
boot::{setup_composefs_bls_boot, setup_composefs_uki_boot, BootSetupType, BootType},
8-
repo::pull_composefs_repo,
9-
service::start_finalize_stated_svc,
10-
state::write_composefs_state,
6+
state::update_target_imgref_in_origin,
117
status::get_composefs_status,
8+
update::{do_upgrade, is_image_pulled, validate_update, UpdateAction},
129
},
1310
cli::{imgref_for_switch, SwitchOpts},
1411
store::{BootedComposefs, Storage},
@@ -42,44 +39,39 @@ pub(crate) async fn switch_composefs(
4239
anyhow::bail!("Target image is undefined")
4340
};
4441

45-
start_finalize_stated_svc()?;
42+
let repo = &*booted_cfs.repo;
43+
let (image, manifest, _) = is_image_pulled(repo, &target_imgref).await?;
4644

47-
let (repo, entries, id, fs) =
48-
pull_composefs_repo(&target_imgref.transport, &target_imgref.image).await?;
45+
if let Some(cfg_verity) = image {
46+
let action = validate_update(
47+
storage,
48+
booted_cfs,
49+
&host,
50+
manifest.config().digest().digest(),
51+
&cfg_verity,
52+
true,
53+
)?;
4954

50-
let Some(entry) = entries.iter().next() else {
51-
anyhow::bail!("No boot entries!");
52-
};
55+
match action {
56+
UpdateAction::Skip => {
57+
println!("No changes in image: {target_imgref:#}");
58+
return Ok(());
59+
}
5360

54-
let boot_type = BootType::from(entry);
55-
let mut boot_digest = None;
61+
UpdateAction::Proceed => {
62+
return do_upgrade(storage, &host, &target_imgref).await;
63+
}
5664

57-
match boot_type {
58-
BootType::Bls => {
59-
boot_digest = Some(setup_composefs_bls_boot(
60-
BootSetupType::Upgrade((storage, &fs, &host)),
61-
repo,
62-
&id,
63-
entry,
64-
)?)
65+
UpdateAction::UpdateOrigin => {
66+
// The staged image will never be the current image's verity digest
67+
println!("Image already in compoesfs repository");
68+
println!("Updating target image reference");
69+
return update_target_imgref_in_origin(storage, booted_cfs, &target_imgref);
70+
}
6571
}
66-
BootType::Uki => setup_composefs_uki_boot(
67-
BootSetupType::Upgrade((storage, &fs, &host)),
68-
repo,
69-
&id,
70-
entries,
71-
)?,
72-
};
72+
}
7373

74-
// TODO: Remove this hardcoded path when write_composefs_state accepts a Dir
75-
write_composefs_state(
76-
&Utf8PathBuf::from("/sysroot"),
77-
id,
78-
&target_imgref,
79-
true,
80-
boot_type,
81-
boot_digest,
82-
)?;
74+
do_upgrade(storage, &host, &target_imgref).await?;
8375

8476
Ok(())
8577
}

crates/lib/src/bootc_composefs/update.rs

Lines changed: 67 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use crate::{
1515
boot::{setup_composefs_bls_boot, setup_composefs_uki_boot, BootSetupType, BootType},
1616
repo::{get_imgref, pull_composefs_repo},
1717
service::start_finalize_stated_svc,
18-
state::{update_target_imgref_in_origin, write_composefs_state},
18+
state::write_composefs_state,
1919
status::{get_bootloader, get_composefs_status, get_container_manifest_and_config},
2020
},
2121
cli::UpgradeOpts,
@@ -49,7 +49,7 @@ pub fn str_to_sha256digest(id: &str) -> Result<Sha256Digest> {
4949
/// * The container image manifest
5050
/// * The container image configuration
5151
#[context("Checking if image {} is pulled", imgref.image)]
52-
async fn is_image_pulled(
52+
pub(crate) async fn is_image_pulled(
5353
repo: &ComposefsRepository,
5454
imgref: &ImageReference,
5555
) -> Result<(Option<Sha512HashValue>, ImageManifest, ImageConfiguration)> {
@@ -75,41 +75,62 @@ fn rm_staged_type1_ent(boot_dir: &Dir) -> Result<()> {
7575
Ok(())
7676
}
7777

78+
#[derive(Debug)]
7879
pub(crate) enum UpdateAction {
7980
/// Skip the update. We probably have the update in our deployments
8081
Skip,
8182
/// Proceed with the update
8283
Proceed,
8384
/// Only update the target imgref in the .origin file
85+
/// Will only be returned if the Operation is update and not switch
8486
UpdateOrigin,
8587
}
8688

8789
/// Determines what action should be taken for the update
88-
fn validate_update(
90+
///
91+
/// Cases:
92+
///
93+
/// - The verity is the same as that of the currently booted deployment
94+
///
95+
/// Nothing to do here as we're currently booted
96+
///
97+
/// - The verity is the same as that of the staged deployment
98+
///
99+
/// Nothing to do, as we only get a "staged" deployment if we have
100+
/// /run/composefs/staged-deployment which is the last thing we create while upgrading
101+
///
102+
/// - The verity is the same as that of the rollback deployment
103+
///
104+
/// Nothing to do since this is a rollback deployment which means this was unstaged at some
105+
/// point
106+
///
107+
/// - The verity is not found
108+
///
109+
/// The update/switch might've been canceled before /run/composefs/staged-deployment
110+
/// was created, or at any other point in time, or it's a new one.
111+
/// Any which way, we can overwrite everything
112+
///
113+
/// # Arguments
114+
///
115+
/// * `storage` - The global storage object
116+
/// * `booted_cfs` - Reference to the booted composefs deployment
117+
/// * `host` - Object returned by `get_composefs_status`
118+
/// * `img_digest` - The SHA256 sum of the target image
119+
/// * `config_verity` - The verity of the Image config splitstream
120+
/// * `is_switch` - Whether this is an update operation or a switch operation
121+
///
122+
/// # Returns
123+
/// * UpdateAction::Skip - Skip the update/switch as we have it as a deployment
124+
/// * UpdateAction::UpdateOrigin - Just update the target imgref in the origin file
125+
/// * UpdateAction::Proceed - Proceed with the update
126+
pub(crate) fn validate_update(
89127
storage: &Storage,
90128
booted_cfs: &BootedComposefs,
91129
host: &Host,
92130
img_digest: &str,
93131
config_verity: &Sha512HashValue,
132+
is_switch: bool,
94133
) -> Result<UpdateAction> {
95-
// Cases
96-
//
97-
// 1. The verity is the same as that of the currently booted deployment
98-
// - Nothing to do here as we're currently booted
99-
//
100-
// 2. The verity is the same as that of the staged deployment
101-
// - Nothing to do, as we only get a "staged" deployment if we have
102-
// /run/composefs/staged-deployment which is the last thing we create while upgrading
103-
//
104-
// 3. The verity is the same as that of the rollback deployment
105-
// - Nothing to do since this is a rollback deployment which means this was unstaged at some
106-
// point
107-
//
108-
// 4. The verity is not found
109-
// - The update/switch might've been canceled before /run/composefs/staged-deployment
110-
// was created, or at any other point in time, or it's a new one.
111-
// Any which way, we can overwrite everything
112-
113134
let repo = &*booted_cfs.repo;
114135

115136
let mut fs = create_filesystem(repo, img_digest, Some(config_verity))?;
@@ -126,8 +147,13 @@ fn validate_update(
126147
//
127148
// We could simply update the image origin file here
128149
if image_id.to_hex() == *booted_cfs.cmdline.digest {
129-
// update_target_imgref_in_origin(storage, booted_cfs);
130-
return Ok(UpdateAction::UpdateOrigin);
150+
let ret = if is_switch {
151+
UpdateAction::UpdateOrigin
152+
} else {
153+
UpdateAction::Skip
154+
};
155+
156+
return Ok(ret);
131157
}
132158

133159
let all_deployments = host.all_composefs_deployments()?;
@@ -178,7 +204,13 @@ fn validate_update(
178204
Ok(UpdateAction::Proceed)
179205
}
180206

181-
async fn do_upgrade(storage: &Storage, host: &Host, imgref: &ImageReference) -> Result<()> {
207+
/// Performs the Update or Switch operation
208+
#[context("Performing Upgrade Operation")]
209+
pub(crate) async fn do_upgrade(
210+
storage: &Storage,
211+
host: &Host,
212+
imgref: &ImageReference,
213+
) -> Result<()> {
182214
start_finalize_stated_svc()?;
183215

184216
let (repo, entries, id, fs) = pull_composefs_repo(&imgref.transport, &imgref.image).await?;
@@ -275,6 +307,7 @@ pub(crate) async fn upgrade_composefs(
275307
&host,
276308
manifest.config().digest().digest(),
277309
&cfg_verity,
310+
false,
278311
)?;
279312

280313
match action {
@@ -288,16 +321,22 @@ pub(crate) async fn upgrade_composefs(
288321
}
289322

290323
UpdateAction::UpdateOrigin => {
291-
// The staged image will never be the current image's verity digest
292-
anyhow::bail!("Staged image verity digest is the same as booted image")
324+
anyhow::bail!("Updating origin not supported for update operation")
293325
}
294326
}
295327
}
296328
}
297329

298330
// We already have this container config
299331
if let Some(cfg_verity) = img_pulled {
300-
let action = validate_update(storage, composefs, &host, &booted_img_digest, &cfg_verity)?;
332+
let action = validate_update(
333+
storage,
334+
composefs,
335+
&host,
336+
&booted_img_digest,
337+
&cfg_verity,
338+
false,
339+
)?;
301340

302341
match action {
303342
UpdateAction::Skip => {
@@ -310,7 +349,7 @@ pub(crate) async fn upgrade_composefs(
310349
}
311350

312351
UpdateAction::UpdateOrigin => {
313-
return update_target_imgref_in_origin(storage, composefs, booted_imgref);
352+
anyhow::bail!("Updating origin not supported for update operation")
314353
}
315354
}
316355
}

0 commit comments

Comments
 (0)