blue_build_process_management/
drivers.rs1use 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#[derive(Default, Clone, Copy, Debug, Builder, Args)]
94pub struct DriverArgs {
95 #[arg(short = 'B', long, env = BB_BUILD_DRIVER)]
98 pub build_driver: Option<BuildDriverType>,
99
100 #[arg(short = 'I', long, env = BB_INSPECT_DRIVER)]
103 pub inspect_driver: Option<InspectDriverType>,
104
105 #[arg(short = 'S', long, env = BB_SIGNING_DRIVER)]
108 pub signing_driver: Option<SigningDriverType>,
109
110 #[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 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 #[must_use]
195 pub fn get_build_id() -> Uuid {
196 trace!("Driver::get_build_id()");
197 *BUILD_ID
198 }
199
200 #[builder]
212 pub fn get_os_version(
213 oci_ref: &Reference,
215 ) -> Result<u64> {
216 trace!("Driver::get_os_version({oci_ref:#?})");
217
218 #[cfg(test)]
219 {
220 let _ = oci_ref; 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}