Skip to content

Commit b33cb5c

Browse files
Johan-Liebert1cgwalters
authored andcommitted
composefs: Ensure idempotency for switch
Similar to how we handle bootc update Signed-off-by: Pragyan Poudyal <[email protected]>
1 parent 71f5ace commit b33cb5c

File tree

2 files changed

+96
-74
lines changed

2 files changed

+96
-74
lines changed
Lines changed: 29 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,11 @@
11
use anyhow::{Context, Result};
2-
use camino::Utf8PathBuf;
3-
use cap_std_ext::cap_std::fs::Dir;
4-
use composefs::fsverity::FsVerityHashValue;
52
use fn_error_context::context;
63

74
use crate::{
85
bootc_composefs::{
9-
boot::{setup_composefs_bls_boot, setup_composefs_uki_boot, BootSetupType, BootType},
10-
repo::pull_composefs_repo,
11-
service::start_finalize_stated_svc,
12-
state::write_composefs_state,
6+
state::update_target_imgref_in_origin,
137
status::get_composefs_status,
8+
update::{do_upgrade, is_image_pulled, validate_update, UpdateAction},
149
},
1510
cli::{imgref_for_switch, SwitchOpts},
1611
store::{BootedComposefs, Storage},
@@ -44,51 +39,39 @@ pub(crate) async fn switch_composefs(
4439
anyhow::bail!("Target image is undefined")
4540
};
4641

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

49-
let (repo, entries, id, fs) =
50-
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+
)?;
5154

52-
let Some(entry) = entries.iter().next() else {
53-
anyhow::bail!("No boot entries!");
54-
};
55-
56-
let boot_type = BootType::from(entry);
57-
let mut boot_digest = None;
55+
match action {
56+
UpdateAction::Skip => {
57+
println!("No changes in image: {target_imgref:#}");
58+
return Ok(());
59+
}
5860

59-
let mounted_fs = Dir::reopen_dir(
60-
&repo
61-
.mount(&id.to_hex())
62-
.context("Failed to mount composefs image")?,
63-
)?;
61+
UpdateAction::Proceed => {
62+
return do_upgrade(storage, &host, &target_imgref).await;
63+
}
6464

65-
match boot_type {
66-
BootType::Bls => {
67-
boot_digest = Some(setup_composefs_bls_boot(
68-
BootSetupType::Upgrade((storage, &fs, &host)),
69-
repo,
70-
&id,
71-
entry,
72-
&mounted_fs,
73-
)?)
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+
}
7471
}
75-
BootType::Uki => setup_composefs_uki_boot(
76-
BootSetupType::Upgrade((storage, &fs, &host)),
77-
repo,
78-
&id,
79-
entries,
80-
)?,
81-
};
72+
}
8273

83-
// TODO: Remove this hardcoded path when write_composefs_state accepts a Dir
84-
write_composefs_state(
85-
&Utf8PathBuf::from("/sysroot"),
86-
id,
87-
&target_imgref,
88-
true,
89-
boot_type,
90-
boot_digest,
91-
)?;
74+
do_upgrade(storage, &host, &target_imgref).await?;
9275

9376
Ok(())
9477
}

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?;
@@ -282,6 +314,7 @@ pub(crate) async fn upgrade_composefs(
282314
&host,
283315
manifest.config().digest().digest(),
284316
&cfg_verity,
317+
false,
285318
)?;
286319

287320
match action {
@@ -295,16 +328,22 @@ pub(crate) async fn upgrade_composefs(
295328
}
296329

297330
UpdateAction::UpdateOrigin => {
298-
// The staged image will never be the current image's verity digest
299-
anyhow::bail!("Staged image verity digest is the same as booted image")
331+
anyhow::bail!("Updating origin not supported for update operation")
300332
}
301333
}
302334
}
303335
}
304336

305337
// We already have this container config
306338
if let Some(cfg_verity) = img_pulled {
307-
let action = validate_update(storage, composefs, &host, &booted_img_digest, &cfg_verity)?;
339+
let action = validate_update(
340+
storage,
341+
composefs,
342+
&host,
343+
&booted_img_digest,
344+
&cfg_verity,
345+
false,
346+
)?;
308347

309348
match action {
310349
UpdateAction::Skip => {
@@ -317,7 +356,7 @@ pub(crate) async fn upgrade_composefs(
317356
}
318357

319358
UpdateAction::UpdateOrigin => {
320-
return update_target_imgref_in_origin(storage, composefs, booted_imgref);
359+
anyhow::bail!("Updating origin not supported for update operation")
321360
}
322361
}
323362
}

0 commit comments

Comments
 (0)