bootc_lib/bootc_composefs/
switch.rs1use anyhow::{Context, Result};
2use fn_error_context::context;
3
4use crate::{
5 bootc_composefs::{
6 status::get_composefs_status,
7 update::{DoUpgradeOpts, UpdateAction, do_upgrade, is_image_pulled, validate_update},
8 },
9 cli::{SwitchOpts, imgref_for_switch},
10 progress_jsonl::ProgressWriter,
11 store::{BootedComposefs, Storage},
12};
13
14#[context("Composefs Switching")]
15pub(crate) async fn switch_composefs(
16 opts: SwitchOpts,
17 storage: &Storage,
18 booted_cfs: &BootedComposefs,
19) -> Result<()> {
20 let target = imgref_for_switch(&opts)?;
21
22 let host = get_composefs_status(storage, booted_cfs)
24 .await
25 .context("Getting composefs deployment status")?;
26
27 let new_spec = {
28 let mut new_spec = host.spec.clone();
29 new_spec.image = Some(target.clone());
30 new_spec
31 };
32
33 if new_spec == host.spec {
34 println!("Image specification is unchanged.");
35 if opts.apply && host.status.staged.is_some() {
36 crate::reboot::reboot()?;
37 }
38 return Ok(());
39 }
40
41 let Some(target_imgref) = new_spec.image else {
42 anyhow::bail!("Target image is undefined")
43 };
44
45 const COMPOSEFS_SWITCH_JOURNAL_ID: &str = "7a6b5c4d3e2f1a0b9c8d7e6f5a4b3c2d1";
46
47 tracing::info!(
48 message_id = COMPOSEFS_SWITCH_JOURNAL_ID,
49 bootc.operation = "switch",
50 bootc.target_image = target_imgref.to_string(),
51 bootc.apply_mode = opts.apply,
52 "Starting composefs switch operation",
53 );
54
55 let repo = &*booted_cfs.repo;
56 let (image, img_config) = is_image_pulled(repo, &target_imgref).await?;
57
58 let use_unified = if opts.unified_storage_exp {
63 true
64 } else {
65 let booted_imgref = host.spec.image.as_ref();
66 let booted_unified = if let Some(booted) = booted_imgref {
67 crate::deploy::image_exists_in_unified_storage(storage, booted).await?
68 } else {
69 false
70 };
71 let target_unified =
72 crate::deploy::image_exists_in_unified_storage(storage, &target_imgref).await?;
73 booted_unified || target_unified
74 };
75
76 let prog: ProgressWriter = opts.progress.try_into()?;
77
78 let do_upgrade_opts = DoUpgradeOpts {
79 soft_reboot: opts.soft_reboot,
80 apply: opts.apply,
81 download_only: false,
82 use_unified,
83 quiet: opts.quiet,
84 prog,
85 };
86
87 if let Some(cfg_verity) = image {
88 let action = validate_update(
89 storage,
90 booted_cfs,
91 &host,
92 img_config.manifest.config().digest().as_ref(),
93 &cfg_verity,
94 true,
95 )?;
96
97 match action {
98 UpdateAction::Skip => {
99 println!("No changes in image: {target_imgref:#}");
100 return Ok(());
101 }
102
103 UpdateAction::Proceed => {
104 return do_upgrade(
105 storage,
106 booted_cfs,
107 &host,
108 &target_imgref,
109 &do_upgrade_opts,
110 &img_config.manifest,
111 )
112 .await;
113 }
114 }
115 }
116
117 do_upgrade(
118 storage,
119 booted_cfs,
120 &host,
121 &target_imgref,
122 &do_upgrade_opts,
123 &img_config.manifest,
124 )
125 .await?;
126
127 Ok(())
128}