Skip to main content

blue_build/commands/
switch.rs

1use std::path::PathBuf;
2
3use blue_build_process_management::drivers::{
4    BootDriver, BuildDriver, CiDriver, Driver, DriverArgs, ImageStorageDriver, PodmanDriver,
5    opts::{BuildOpts, GenerateImageNameOpts, RemoveImageOpts, SwitchOpts},
6    types::{BuildDriverType, RunDriverType},
7};
8use blue_build_recipe::Recipe;
9use blue_build_utils::{constants::BB_SKIP_VALIDATION, container::ImageRef};
10use bon::Builder;
11use clap::Args;
12use log::trace;
13use miette::{IntoDiagnostic, Result, bail};
14use tempfile::TempDir;
15
16use crate::commands::generate::GenerateCommand;
17
18use super::BlueBuildCommand;
19
20#[derive(Default, Clone, Debug, Builder, Args)]
21pub struct SwitchCommand {
22    /// The recipe file to build an image.
23    #[arg()]
24    recipe: PathBuf,
25
26    /// Reboot your system after
27    /// the update is complete.
28    #[arg(short, long)]
29    #[builder(default)]
30    reboot: bool,
31
32    /// The location to temporarily store files
33    /// while building. If unset, it will use `/tmp`.
34    #[arg(long)]
35    tempdir: Option<PathBuf>,
36
37    /// Skips validation of the recipe file.
38    #[arg(long, env = BB_SKIP_VALIDATION)]
39    #[builder(default)]
40    skip_validation: bool,
41
42    #[clap(flatten)]
43    #[builder(default)]
44    drivers: DriverArgs,
45}
46
47impl BlueBuildCommand for SwitchCommand {
48    fn try_run(&mut self) -> Result<()> {
49        trace!("SwitchCommand::try_run()");
50
51        Driver::init(
52            DriverArgs::builder()
53                .build_driver(BuildDriverType::Podman)
54                .run_driver(RunDriverType::Podman)
55                .maybe_boot_driver(self.drivers.boot_driver)
56                .maybe_signing_driver(self.drivers.signing_driver)
57                .build(),
58        );
59
60        let status = Driver::status()?;
61
62        if status.transaction_in_progress() {
63            bail!("There is a transaction in progress. Please cancel it using `rpm-ostree cancel`");
64        }
65
66        let recipe = Recipe::parse(&self.recipe)?;
67        let image_name = Driver::generate_image_name(
68            GenerateImageNameOpts::builder()
69                .name(recipe.name.trim())
70                .build(),
71        )?;
72        let tempdir = if let Some(ref dir) = self.tempdir {
73            TempDir::new_in(dir).into_diagnostic()?
74        } else {
75            TempDir::new().into_diagnostic()?
76        };
77        let containerfile = tempdir
78            .path()
79            .join(blue_build_utils::generate_containerfile_path(&self.recipe)?);
80
81        GenerateCommand::builder()
82            .output(&containerfile)
83            .recipe(&self.recipe)
84            .build()
85            .try_run()?;
86        Driver::build(
87            BuildOpts::builder()
88                .image(&ImageRef::from(&image_name))
89                .containerfile(&containerfile)
90                .secrets(&recipe.get_secrets())
91                .build(),
92        )?;
93        PodmanDriver::copy_image_to_root_store(&image_name)?;
94        PodmanDriver::remove_image(RemoveImageOpts::builder().image(&image_name).build())?;
95
96        if status
97            .booted_image()
98            .is_some_and(|booted| booted == image_name)
99        {
100            Driver::upgrade(
101                SwitchOpts::builder()
102                    .image(&image_name)
103                    .reboot(self.reboot)
104                    .build(),
105            )
106        } else {
107            Driver::switch(
108                SwitchOpts::builder()
109                    .image(&image_name)
110                    .reboot(self.reboot)
111                    .build(),
112            )
113        }
114    }
115}