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::{
8 DoUpgradeOpts, UpdateAction, apply_upgrade_from_downloaded,
9 discard_failed_aboot_update, do_upgrade, ensure_delta_source_present, is_image_pulled,
10 lookup_config_splitstream, validate_update,
11 },
12 },
13 cli::{SwitchOpts, imgref_for_switch},
14 progress_jsonl::ProgressWriter,
15 store::{BootedComposefs, Storage},
16};
17
18#[context("Composefs Switching")]
19pub(crate) async fn switch_composefs(
20 opts: SwitchOpts,
21 storage: &Storage,
22 booted_cfs: &BootedComposefs,
23) -> Result<()> {
24 let mut host = get_composefs_status(storage, booted_cfs)
26 .await
27 .context("Getting composefs deployment status")?;
28
29 let delta = crate::delta::open_opt(opts.from_delta.as_deref()).await?;
30
31 let prog: ProgressWriter = opts.progress.clone().try_into()?;
32
33 let mut do_upgrade_opts = DoUpgradeOpts {
34 soft_reboot: opts.soft_reboot,
35 apply: opts.apply,
36 download_only: opts.download_opts.download_only,
37 use_unified: false,
38 quiet: opts.quiet,
39 prog,
40 delta: delta.as_ref(),
41 uki_addon_opts: opts.uki_addon_opts.clone(),
42 };
43
44 if opts.download_opts.from_downloaded {
45 return apply_upgrade_from_downloaded(storage, booted_cfs, &host, &do_upgrade_opts).await;
46 }
47
48 let target = imgref_for_switch(&opts)?;
49 if let Some(delta) = delta.as_ref() {
50 delta.validate_image_reference(&target)?;
51 }
52
53 let failed_target_matches = host
57 .status
58 .staged
59 .as_ref()
60 .and_then(|entry| entry.image.as_ref())
61 .is_some_and(|image| image.image == target);
62 let discarded_failed_update = discard_failed_aboot_update(storage, &host)?;
63 if discarded_failed_update {
64 host = get_composefs_status(storage, booted_cfs).await?;
66 }
67 let retry_failed_target = discarded_failed_update && failed_target_matches;
68
69 let new_spec = {
70 let mut new_spec = host.spec.clone();
71 new_spec.image = Some(target.clone());
72 new_spec
73 };
74
75 if new_spec == host.spec && !retry_failed_target {
76 println!("Image specification is unchanged.");
77 if opts.apply && host.status.staged.is_some() {
78 crate::reboot::reboot()?;
79 }
80 return Ok(());
81 }
82
83 let Some(target_imgref) = new_spec.image else {
84 anyhow::bail!("Target image is undefined")
85 };
86
87 const COMPOSEFS_SWITCH_JOURNAL_ID: &str = "7a6b5c4d3e2f1a0b9c8d7e6f5a4b3c2d1";
88
89 tracing::info!(
90 message_id = COMPOSEFS_SWITCH_JOURNAL_ID,
91 bootc.operation = "switch",
92 bootc.target_image = target_imgref.to_string(),
93 bootc.apply_mode = opts.apply,
94 bootc.download_only = opts.download_opts.download_only,
95 bootc.from_downloaded = opts.download_opts.from_downloaded,
96 "Starting composefs switch operation",
97 );
98
99 let repo = &*booted_cfs.repo;
100
101 do_upgrade_opts.use_unified = if opts.unified_storage_exp {
106 true
107 } else {
108 let booted_imgref = host.spec.image.as_ref();
109 let booted_unified = if let Some(booted) = booted_imgref {
110 crate::deploy::image_exists_in_unified_storage(storage, booted).await?
111 } else {
112 false
113 };
114 let target_unified =
115 crate::deploy::image_exists_in_unified_storage(storage, &target_imgref).await?;
116 booted_unified || target_unified
117 };
118
119 let (image, manifest) = match &delta {
122 Some(delta) => {
123 crate::delta::reject_unified_storage(delta, do_upgrade_opts.use_unified)?;
124 ensure_delta_source_present(repo, delta)?;
125 (
126 lookup_config_splitstream(repo, delta.target_manifest().config().digest())?,
127 delta.target_manifest().clone(),
128 )
129 }
130 None => {
131 let (image, img_config, _) = is_image_pulled(repo, &target_imgref).await?;
132 (image, img_config.manifest)
133 }
134 };
135
136 if let Some(cfg_verity) = image {
137 let action = validate_update(
138 storage,
139 booted_cfs,
140 &host,
141 manifest.config().digest().as_ref(),
142 &cfg_verity,
143 true,
144 )?;
145
146 match action {
147 UpdateAction::Skip => {
148 println!("No changes in image: {target_imgref:#}");
149 return Ok(());
150 }
151
152 UpdateAction::Proceed => {
153 return do_upgrade(
154 storage,
155 booted_cfs,
156 &host,
157 &target_imgref,
158 &do_upgrade_opts,
159 &manifest,
160 )
161 .await;
162 }
163 }
164 }
165
166 do_upgrade(
167 storage,
168 booted_cfs,
169 &host,
170 &target_imgref,
171 &do_upgrade_opts,
172 &manifest,
173 )
174 .await?;
175
176 Ok(())
177}