bootc_lib/bootc_composefs/
switch.rs

1use anyhow::{Context, Result};
2use fn_error_context::context;
3
4use crate::{
5    bootc_composefs::{
6        state::update_target_imgref_in_origin,
7        status::get_composefs_status,
8        update::{DoUpgradeOpts, UpdateAction, do_upgrade, is_image_pulled, validate_update},
9    },
10    cli::{SwitchOpts, imgref_for_switch},
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    // TODO: Handle in-place
23    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        return Ok(());
36    }
37
38    let Some(target_imgref) = new_spec.image else {
39        anyhow::bail!("Target image is undefined")
40    };
41
42    const COMPOSEFS_SWITCH_JOURNAL_ID: &str = "7a6b5c4d3e2f1a0b9c8d7e6f5a4b3c2d1";
43
44    tracing::info!(
45        message_id = COMPOSEFS_SWITCH_JOURNAL_ID,
46        bootc.operation = "switch",
47        bootc.target_image = target_imgref.to_string(),
48        bootc.apply_mode = opts.apply,
49        "Starting composefs switch operation",
50    );
51
52    let repo = &*booted_cfs.repo;
53    let (image, img_config) = is_image_pulled(repo, &target_imgref).await?;
54
55    let do_upgrade_opts = DoUpgradeOpts {
56        soft_reboot: opts.soft_reboot,
57        apply: opts.apply,
58        download_only: false,
59    };
60
61    if let Some(cfg_verity) = image {
62        let action = validate_update(
63            storage,
64            booted_cfs,
65            &host,
66            img_config.manifest.config().digest().digest(),
67            &cfg_verity,
68            true,
69        )?;
70
71        match action {
72            UpdateAction::Skip => {
73                println!("No changes in image: {target_imgref:#}");
74                return Ok(());
75            }
76
77            UpdateAction::Proceed => {
78                return do_upgrade(
79                    storage,
80                    booted_cfs,
81                    &host,
82                    &target_imgref,
83                    &img_config,
84                    &do_upgrade_opts,
85                )
86                .await;
87            }
88
89            UpdateAction::UpdateOrigin => {
90                // The staged image will never be the current image's verity digest
91                println!("Image already in composefs repository");
92                println!("Updating target image reference");
93                return update_target_imgref_in_origin(storage, booted_cfs, &target_imgref);
94            }
95        }
96    }
97
98    do_upgrade(
99        storage,
100        booted_cfs,
101        &host,
102        &target_imgref,
103        &img_config,
104        &do_upgrade_opts,
105    )
106    .await?;
107
108    Ok(())
109}