Skip to main content

blue_build_process_management/
drivers.rs

1//! This module is responsible for managing various strategies
2//! to perform actions throughout the program.
3//!
4//! This hides all
5//! the implementation details from the command logic and allows
6//! for caching certain long execution tasks like inspecting the
7//! labels for an image.
8
9use std::{
10    borrow::Borrow,
11    fmt::Debug,
12    process::{ExitStatus, Output},
13    sync::{LazyLock, RwLock, atomic::AtomicBool},
14    time::Duration,
15};
16
17use blue_build_utils::{
18    BUILD_ID,
19    constants::{
20        BB_BOOT_DRIVER, BB_BUILD_DRIVER, BB_INSPECT_DRIVER, BB_RUN_DRIVER, BB_SIGNING_DRIVER,
21    },
22    container::{ContainerId, ImageRef, MountId, Tag},
23    semver::Version,
24};
25use bon::{Builder, bon};
26use cached::proc_macro::cached;
27use clap::Args;
28use colored::Colorize;
29use indicatif::{ProgressBar, ProgressStyle};
30use log::{info, trace, warn};
31use miette::{Context, Result};
32use oci_client::Reference;
33use uuid::Uuid;
34
35use crate::{logging::Logger, signal_handler::DetachedContainer};
36use opts::{
37    BuildChunkedOciOpts, BuildOpts, BuildRechunkTagPushOpts, BuildTagPushOpts, CheckKeyPairOpts,
38    ContainerOpts, CopyOciOpts, CreateContainerOpts, GenerateImageNameOpts, GenerateKeyPairOpts,
39    GenerateTagsOpts, GetMetadataOpts, ManifestCreateOpts, ManifestPushOpts, PruneOpts, PullOpts,
40    PushOpts, RechunkOpts, RemoveContainerOpts, RemoveImageOpts, RunOpts, SignOpts, SwitchOpts,
41    TagOpts, UntagOpts, VerifyOpts, VolumeOpts,
42};
43use types::{
44    BootDriverType, BuildDriverType, CiDriverType, ImageMetadata, InspectDriverType, RunDriverType,
45    SigningDriverType,
46};
47
48pub use self::{
49    buildah_driver::BuildahDriver, cosign_driver::CosignDriver, docker_driver::DockerDriver,
50    github_driver::GithubDriver, gitlab_driver::GitlabDriver, local_driver::LocalDriver,
51    oci_client_driver::OciClientDriver, podman_driver::PodmanDriver,
52    rpm_ostree_driver::RpmOstreeDriver, rpm_ostree_runner::RpmOstreeRunner,
53    sigstore_driver::SigstoreDriver, skopeo_driver::SkopeoDriver, traits::*,
54};
55
56#[cfg(feature = "bootc")]
57pub use bootc_driver::BootcDriver;
58
59#[cfg(feature = "bootc")]
60mod bootc_driver;
61mod buildah_driver;
62mod cosign_driver;
63mod docker_driver;
64mod github_driver;
65mod gitlab_driver;
66mod local_driver;
67mod oci_client_driver;
68pub mod opts;
69mod podman_driver;
70mod rpm_ostree_driver;
71mod rpm_ostree_runner;
72mod sigstore_driver;
73mod skopeo_driver;
74mod traits;
75pub mod types;
76
77static INIT: AtomicBool = AtomicBool::new(false);
78static SELECTED_BUILD_DRIVER: LazyLock<RwLock<Option<BuildDriverType>>> =
79    LazyLock::new(|| RwLock::new(None));
80static SELECTED_RUN_DRIVER: LazyLock<RwLock<Option<RunDriverType>>> =
81    LazyLock::new(|| RwLock::new(None));
82static SELECTED_SIGNING_DRIVER: LazyLock<RwLock<Option<SigningDriverType>>> =
83    LazyLock::new(|| RwLock::new(None));
84static SELECTED_CI_DRIVER: LazyLock<RwLock<Option<CiDriverType>>> =
85    LazyLock::new(|| RwLock::new(None));
86static SELECTED_BOOT_DRIVER: LazyLock<RwLock<Option<BootDriverType>>> =
87    LazyLock::new(|| RwLock::new(None));
88
89/// Args for selecting the various drivers to use for runtime.
90///
91/// If the args are left uninitialized, the program will determine
92/// the best one available.
93#[derive(Default, Clone, Copy, Debug, Builder, Args)]
94pub struct DriverArgs {
95    /// Select which driver to use to build
96    /// your image.
97    #[arg(short = 'B', long, env = BB_BUILD_DRIVER)]
98    pub build_driver: Option<BuildDriverType>,
99
100    /// Select which driver to use to inspect
101    /// images.
102    #[arg(short = 'I', long, env = BB_INSPECT_DRIVER)]
103    pub inspect_driver: Option<InspectDriverType>,
104
105    /// Select which driver to use to sign
106    /// images.
107    #[arg(short = 'S', long, env = BB_SIGNING_DRIVER)]
108    pub signing_driver: Option<SigningDriverType>,
109
110    /// Select which driver to use to run
111    /// containers.
112    #[arg(short = 'R', long, env = BB_RUN_DRIVER)]
113    pub run_driver: Option<RunDriverType>,
114
115    #[arg(short = 'T', long, env = BB_BOOT_DRIVER)]
116    pub boot_driver: Option<BootDriverType>,
117}
118
119macro_rules! impl_driver_type {
120    ($cache:ident) => {{
121        let lock = $cache.read().expect("Should read");
122        lock.expect("Driver should have initialized build driver")
123    }};
124}
125
126macro_rules! impl_driver_init {
127    (@) => { };
128    ($init:ident; $($tail:tt)*) => {
129        {
130            if $init.compare_exchange(
131                false,
132                true,
133                std::sync::atomic::Ordering::AcqRel,
134                std::sync::atomic::Ordering::Acquire
135            ).is_ok() {
136                impl_driver_init!(@ $($tail)*);
137            }
138        }
139    };
140    (@ default => $cache:ident; $($tail:tt)*) => {
141        {
142            let mut driver = $cache.write().expect("Should lock");
143
144            impl_driver_init!(@ $($tail)*);
145
146            *driver = Some(driver.determine_driver());
147            ::log::trace!("Driver set {driver:?}");
148            drop(driver);
149        }
150    };
151    (@ $driver:expr => $cache:ident; $($tail:tt)*) => {
152        {
153            let mut driver = $cache.write().expect("Should lock");
154
155            impl_driver_init!(@ $($tail)*);
156
157            *driver = Some($driver.determine_driver());
158            ::log::trace!("Driver set {driver:?}");
159            drop(driver);
160        }
161    };
162}
163
164pub struct Driver;
165
166#[bon]
167impl Driver {
168    /// Initializes the Strategy with user provided credentials.
169    ///
170    /// If you want to take advantage of a user's credentials,
171    /// you will want to run init before trying to use any of
172    /// the strategies.
173    ///
174    /// # Panics
175    /// Will panic if it is unable to initialize drivers.
176    pub fn init(mut args: DriverArgs) {
177        trace!("Driver::init()");
178
179        if args.inspect_driver.is_some() {
180            warn!("Setting the inspect driver is deprecated.");
181        }
182
183        impl_driver_init! {
184            INIT;
185            args.build_driver => SELECTED_BUILD_DRIVER;
186            args.run_driver => SELECTED_RUN_DRIVER;
187            args.signing_driver => SELECTED_SIGNING_DRIVER;
188            args.boot_driver => SELECTED_BOOT_DRIVER;
189            default => SELECTED_CI_DRIVER;
190        }
191    }
192
193    /// Gets the current build's UUID
194    #[must_use]
195    pub fn get_build_id() -> Uuid {
196        trace!("Driver::get_build_id()");
197        *BUILD_ID
198    }
199
200    /// Retrieve the `os_version` for an image.
201    ///
202    /// This gets cached for faster resolution if it's required
203    /// in another part of the program.
204    ///
205    /// # Errors
206    /// Will error if the image doesn't have OS version info
207    /// or we are unable to lock a mutex.
208    ///
209    /// # Panics
210    /// Panics if the mutex fails to lock.
211    #[builder]
212    pub fn get_os_version(
213        /// The OCI image reference.
214        oci_ref: &Reference,
215    ) -> Result<u64> {
216        trace!("Driver::get_os_version({oci_ref:#?})");
217
218        #[cfg(test)]
219        {
220            let _ = oci_ref; // silence lint
221
222            if true {
223                return Ok(41);
224            }
225        }
226
227        info!("Retrieving OS version from {oci_ref}");
228
229        let os_version = Self::get_metadata(GetMetadataOpts::builder().image(oci_ref).build())
230            .and_then(|inspection| {
231                trace!("{inspection:?}");
232                inspection.get_version().wrap_err_with(|| {
233                    format!(
234                        "Failed to parse version from metadata for {}",
235                        oci_ref.to_string().bold()
236                    )
237                })
238            })
239            .or_else(|err| {
240                warn!("Unable to get version via image inspection due to error:\n{err:?}");
241                get_version_run_image(oci_ref)
242            })?;
243        trace!("os_version: {os_version}");
244        Ok(os_version.major)
245    }
246
247    pub fn get_build_driver() -> BuildDriverType {
248        impl_driver_type!(SELECTED_BUILD_DRIVER)
249    }
250
251    pub fn get_signing_driver() -> SigningDriverType {
252        impl_driver_type!(SELECTED_SIGNING_DRIVER)
253    }
254
255    pub fn get_run_driver() -> RunDriverType {
256        impl_driver_type!(SELECTED_RUN_DRIVER)
257    }
258
259    pub fn get_ci_driver() -> CiDriverType {
260        impl_driver_type!(SELECTED_CI_DRIVER)
261    }
262
263    pub fn get_boot_driver() -> BootDriverType {
264        impl_driver_type!(SELECTED_BOOT_DRIVER)
265    }
266}
267
268#[cached(
269    result = true,
270    key = "String",
271    convert = r#"{ oci_ref.to_string() }"#,
272    sync_writes = "by_key"
273)]
274fn get_version_run_image(oci_ref: &Reference) -> Result<Version> {
275    warn!(concat!(
276        "Pulling and running the image to retrieve the version. ",
277        "This will take a while..."
278    ));
279
280    let progress = Logger::multi_progress().add(
281        ProgressBar::new_spinner()
282            .with_style(ProgressStyle::default_spinner())
283            .with_message(format!(
284                "Pulling image {} to get version",
285                oci_ref.to_string().bold()
286            )),
287    );
288    progress.enable_steady_tick(Duration::from_millis(100));
289
290    let should_remove = if matches!(Driver::get_run_driver(), RunDriverType::Docker) {
291        !Driver::list_images(false)?.contains(oci_ref)
292    } else {
293        false
294    };
295
296    let output = Driver::run_output(
297        RunOpts::builder()
298            .image(&oci_ref.to_string())
299            .args(&bon::vec![
300                "/bin/bash",
301                "-c",
302                r#"awk -F= '/^VERSION_ID=/ {gsub(/"/, "", $2); print $2}' /usr/lib/os-release"#,
303            ])
304            .pull(true)
305            .remove(true)
306            .build(),
307    )?;
308
309    if should_remove {
310        Driver::remove_image(RemoveImageOpts::builder().image(oci_ref).build())?;
311    }
312
313    progress.finish_and_clear();
314    Logger::multi_progress().remove(&progress);
315
316    String::from_utf8_lossy(&output.stdout).trim().parse()
317}
318
319macro_rules! impl_build_driver {
320    ($func:ident($($args:expr),*)) => {
321        match Self::get_build_driver() {
322            BuildDriverType::Buildah => BuildahDriver::$func($($args,)*),
323            BuildDriverType::Podman => PodmanDriver::$func($($args,)*),
324            BuildDriverType::Docker => DockerDriver::$func($($args,)*),
325        }
326    };
327}
328
329impl ImageStorageDriver for Driver {
330    fn remove_image(opts: RemoveImageOpts) -> Result<()> {
331        impl_build_driver!(remove_image(opts))
332    }
333
334    fn list_images(privileged: bool) -> Result<Vec<Reference>> {
335        impl_build_driver!(list_images(privileged))
336    }
337}
338
339impl BuildDriver for Driver {
340    fn build(opts: BuildOpts) -> Result<()> {
341        impl_build_driver!(build(opts))
342    }
343
344    fn tag(opts: TagOpts) -> Result<()> {
345        impl_build_driver!(tag(opts))
346    }
347
348    fn untag(opts: UntagOpts) -> Result<()> {
349        impl_build_driver!(untag(opts))
350    }
351
352    fn push(opts: PushOpts) -> Result<()> {
353        impl_build_driver!(push(opts))
354    }
355
356    fn pull(opts: PullOpts) -> Result<ContainerId> {
357        impl_build_driver!(pull(opts))
358    }
359
360    fn login(server: &str) -> Result<()> {
361        impl_build_driver!(login(server))
362    }
363
364    fn prune(opts: PruneOpts) -> Result<()> {
365        impl_build_driver!(prune(opts))
366    }
367
368    fn manifest_create(opts: ManifestCreateOpts) -> Result<()> {
369        impl_build_driver!(manifest_create(opts))
370    }
371
372    fn manifest_push(opts: ManifestPushOpts) -> Result<()> {
373        impl_build_driver!(manifest_push(opts))
374    }
375
376    fn build_tag_push(opts: BuildTagPushOpts) -> Result<Vec<String>> {
377        impl_build_driver!(build_tag_push(opts))
378    }
379}
380
381macro_rules! impl_signing_driver {
382    ($func:ident($($args:expr),*)) => {
383        match Self::get_signing_driver() {
384            SigningDriverType::Cosign => CosignDriver::$func($($args,)*),
385            SigningDriverType::Sigstore => SigstoreDriver::$func($($args,)*),
386        }
387    };
388}
389
390impl SigningDriver for Driver {
391    fn generate_key_pair(opts: GenerateKeyPairOpts) -> Result<()> {
392        impl_signing_driver!(generate_key_pair(opts))
393    }
394
395    fn check_signing_files(opts: CheckKeyPairOpts) -> Result<()> {
396        impl_signing_driver!(check_signing_files(opts))
397    }
398
399    fn sign(opts: SignOpts) -> Result<()> {
400        impl_signing_driver!(sign(opts))
401    }
402
403    fn verify(opts: VerifyOpts) -> Result<()> {
404        impl_signing_driver!(verify(opts))
405    }
406
407    fn signing_login(server: &str) -> Result<()> {
408        impl_signing_driver!(signing_login(server))
409    }
410}
411
412impl InspectDriver for Driver {
413    fn get_metadata(opts: GetMetadataOpts) -> Result<ImageMetadata> {
414        OciClientDriver::get_metadata(opts)
415    }
416}
417
418macro_rules! impl_run_driver {
419    ($func:ident($($args:expr),*)) => {
420        match Self::get_run_driver() {
421            RunDriverType::Docker => DockerDriver::$func($($args,)*),
422            RunDriverType::Podman => PodmanDriver::$func($($args,)*),
423        }
424    };
425}
426
427impl RunDriver for Driver {
428    fn run(opts: RunOpts) -> Result<ExitStatus> {
429        impl_run_driver!(run(opts))
430    }
431
432    fn run_output(opts: RunOpts) -> Result<Output> {
433        impl_run_driver!(run_output(opts))
434    }
435
436    fn run_detached(opts: RunOpts) -> Result<DetachedContainer> {
437        impl_run_driver!(run_detached(opts))
438    }
439
440    fn create_container(opts: CreateContainerOpts) -> Result<ContainerId> {
441        impl_run_driver!(create_container(opts))
442    }
443
444    fn remove_container(opts: RemoveContainerOpts) -> Result<()> {
445        impl_run_driver!(remove_container(opts))
446    }
447}
448
449macro_rules! impl_ci_driver {
450    ($func:ident($($args:expr),*)) => {
451        match Self::get_ci_driver() {
452            CiDriverType::Local => LocalDriver::$func($($args,)*),
453            CiDriverType::Gitlab => GitlabDriver::$func($($args,)*),
454            CiDriverType::Github => GithubDriver::$func($($args,)*),
455        }
456    };
457}
458
459impl CiDriver for Driver {
460    fn on_default_branch() -> bool {
461        impl_ci_driver!(on_default_branch())
462    }
463
464    fn keyless_cert_identity() -> Result<String> {
465        impl_ci_driver!(keyless_cert_identity())
466    }
467
468    fn oidc_provider() -> Result<String> {
469        impl_ci_driver!(oidc_provider())
470    }
471
472    fn generate_tags(opts: GenerateTagsOpts) -> Result<Vec<Tag>> {
473        impl_ci_driver!(generate_tags(opts))
474    }
475
476    fn get_repo_url() -> Result<String> {
477        impl_ci_driver!(get_repo_url())
478    }
479
480    fn get_registry() -> Result<String> {
481        impl_ci_driver!(get_registry())
482    }
483
484    fn generate_image_name<'a, O>(opts: O) -> Result<Reference>
485    where
486        O: Borrow<GenerateImageNameOpts<'a>>,
487    {
488        impl_ci_driver!(generate_image_name(opts))
489    }
490
491    fn default_ci_file_path() -> std::path::PathBuf {
492        impl_ci_driver!(default_ci_file_path())
493    }
494}
495
496impl BuildChunkedOciDriver for Driver {
497    fn manifest_create_with_runner(
498        runner: &RpmOstreeRunner,
499        opts: ManifestCreateOpts,
500    ) -> Result<()> {
501        PodmanDriver::manifest_create_with_runner(runner, opts)
502    }
503
504    fn manifest_push_with_runner(runner: &RpmOstreeRunner, opts: ManifestPushOpts) -> Result<()> {
505        PodmanDriver::manifest_push_with_runner(runner, opts)
506    }
507
508    fn pull_with_runner(runner: &RpmOstreeRunner, opts: PullOpts) -> Result<ContainerId> {
509        PodmanDriver::pull_with_runner(runner, opts)
510    }
511
512    fn remove_image_with_runner(runner: &RpmOstreeRunner, image_ref: &str) -> Result<()> {
513        PodmanDriver::remove_image_with_runner(runner, image_ref)
514    }
515
516    fn build_chunked_oci(
517        runner: &RpmOstreeRunner,
518        unchunked_image: &ImageRef<'_>,
519        final_image: &ImageRef<'_>,
520        opts: BuildChunkedOciOpts,
521    ) -> Result<()> {
522        PodmanDriver::build_chunked_oci(runner, unchunked_image, final_image, opts)
523    }
524
525    fn build_rechunk_tag_push(opts: BuildRechunkTagPushOpts) -> Result<Vec<String>> {
526        PodmanDriver::build_rechunk_tag_push(opts)
527    }
528}
529
530impl ContainerMountDriver for Driver {
531    fn mount_container(opts: ContainerOpts) -> Result<MountId> {
532        PodmanDriver::mount_container(opts)
533    }
534
535    fn unmount_container(opts: ContainerOpts) -> Result<()> {
536        PodmanDriver::unmount_container(opts)
537    }
538
539    fn remove_volume(opts: VolumeOpts) -> Result<()> {
540        PodmanDriver::remove_volume(opts)
541    }
542}
543
544impl OciCopy for Driver {
545    fn copy_oci(&self, opts: CopyOciOpts) -> Result<()> {
546        SkopeoDriver.copy_oci(opts)
547    }
548}
549
550impl RechunkDriver for Driver {
551    fn rechunk(opts: RechunkOpts) -> Result<Vec<String>> {
552        PodmanDriver::rechunk(opts)
553    }
554}
555
556macro_rules! impl_boot_driver {
557    ($func:ident($($args:expr),*)) => {
558        match Self::get_boot_driver() {
559            #[cfg(feature = "bootc")]
560            BootDriverType::Bootc => BootcDriver::$func($($args,)*),
561            BootDriverType::RpmOstree => RpmOstreeDriver::$func($($args,)*),
562            BootDriverType::None => ::miette::bail!("Cannot perform boot operation when no boot driver exists."),
563        }
564    };
565}
566
567impl BootDriver for Driver {
568    fn status() -> Result<Box<dyn BootStatus>> {
569        impl_boot_driver!(status())
570    }
571
572    fn switch(opts: SwitchOpts) -> Result<()> {
573        impl_boot_driver!(switch(opts))
574    }
575
576    fn upgrade(opts: SwitchOpts) -> Result<()> {
577        impl_boot_driver!(upgrade(opts))
578    }
579}