libcnb 0.11.4

A framework for writing Cloud Native Buildpacks in Rust
Documentation
//! Provides build phase specific types and helpers.

use crate::buildpack::Buildpack;
use crate::data::buildpack::StackId;
use crate::data::layer::LayerName;
use crate::data::store::Store;
use crate::data::{
    buildpack::SingleBuildpackDescriptor, buildpack_plan::BuildpackPlan, launch::Launch,
};
use crate::layer::{HandleLayerErrorOrBuildpackError, Layer, LayerData};
use crate::sbom::Sbom;
use std::path::PathBuf;

/// Context for the build phase execution.
pub struct BuildContext<B: Buildpack + ?Sized> {
    pub layers_dir: PathBuf,
    pub app_dir: PathBuf,
    pub buildpack_dir: PathBuf,
    pub stack_id: StackId,
    pub platform: B::Platform,
    pub buildpack_plan: BuildpackPlan,
    pub buildpack_descriptor: SingleBuildpackDescriptor<B::Metadata>,
    pub store: Option<Store>,
}

impl<B: Buildpack + ?Sized> BuildContext<B> {
    /// Handles the given [`Layer`] implementation in this context.
    ///
    /// It will ensure that the layer with the given name is created and/or updated accordingly and
    /// handles all errors that can occur during the process. After this method has executed, the
    /// layer will exist on disk or an error has been returned by this method.
    ///
    /// Use the returned [`LayerData`] to access the layers metadata and environment variables for
    /// subsequent logic or layers.
    ///
    /// # Example:
    /// ```
    /// # use libcnb::build::{BuildContext, BuildResult, BuildResultBuilder};
    /// # use libcnb::data::layer_name;
    /// # use libcnb::data::layer_content_metadata::LayerTypes;
    /// # use libcnb::detect::{DetectContext, DetectResult};
    /// # use libcnb::generic::{GenericError, GenericMetadata, GenericPlatform};
    /// # use libcnb::layer::{Layer, LayerResult, LayerResultBuilder};
    /// # use libcnb::Buildpack;
    /// # use serde::Deserialize;
    /// # use serde::Serialize;
    /// # use std::path::Path;
    /// #
    /// struct ExampleBuildpack;
    ///
    /// impl Buildpack for ExampleBuildpack {
    /// #   type Platform = GenericPlatform;
    /// #   type Metadata = GenericMetadata;
    /// #   type Error = GenericError;
    /// #
    /// #    fn detect(&self, context: DetectContext<Self>) -> libcnb::Result<DetectResult, Self::Error> {
    /// #        unimplemented!()
    /// #    }
    /// #
    ///     fn build(&self, context: BuildContext<Self>) -> libcnb::Result<BuildResult, Self::Error> {
    ///         let example_layer = context.handle_layer(layer_name!("example-layer"), ExampleLayer)?;
    ///
    ///         println!(
    ///             "Monologue from layer metadata: {}",
    ///             &example_layer.content_metadata.metadata.monologue
    ///         );
    ///
    ///         BuildResultBuilder::new().build()
    ///     }
    /// }
    ///
    /// struct ExampleLayer;
    ///
    /// # #[derive(Deserialize, Serialize, Clone)]
    /// # struct ExampleLayerMetadata {
    /// #    monologue: String,
    /// # }
    /// #
    /// impl Layer for ExampleLayer {
    /// # type Buildpack = ExampleBuildpack;
    /// #   type Metadata = ExampleLayerMetadata;
    /// #
    /// #    fn types(&self) -> LayerTypes {
    /// #        unimplemented!()
    /// #    }
    /// #
    ///     fn create(
    ///         &self,
    ///         context: &BuildContext<Self::Buildpack>,
    ///         layer_path: &Path,
    ///     ) -> Result<LayerResult<Self::Metadata>, <Self::Buildpack as Buildpack>::Error> {
    ///         LayerResultBuilder::new(ExampleLayerMetadata {
    ///             monologue: String::from("I've seen things you people wouldn't believe... Attack ships on fire off the shoulder of Orion..." )
    ///         }).build()
    ///     }
    /// }
    /// ```
    pub fn handle_layer<L: Layer<Buildpack = B>>(
        &self,
        layer_name: LayerName,
        layer: L,
    ) -> crate::Result<LayerData<L::Metadata>, B::Error> {
        crate::layer::handle_layer(self, layer_name, layer).map_err(|error| match error {
            HandleLayerErrorOrBuildpackError::HandleLayerError(e) => {
                crate::Error::HandleLayerError(e)
            }
            HandleLayerErrorOrBuildpackError::BuildpackError(e) => crate::Error::BuildpackError(e),
        })
    }
}

/// Describes the result of the build phase.
///
/// In contrast to `DetectResult`, it always signals a successful build. To fail the build phase,
/// return a failed [`crate::Result`] from the build function.
///
/// It contains build phase output such as launch and/or store metadata which will be subsequently
/// handled by libcnb.
///
/// To construct values of this type, use a [`BuildResultBuilder`].
#[derive(Debug)]
#[must_use]
pub struct BuildResult(pub(crate) InnerBuildResult);

#[derive(Debug)]
pub(crate) enum InnerBuildResult {
    Pass {
        launch: Option<Launch>,
        store: Option<Store>,
        build_sboms: Vec<Sbom>,
        launch_sboms: Vec<Sbom>,
    },
}

/// Constructs [`BuildResult`] values.
///
/// # Examples:
/// ```
/// use libcnb::build::{BuildResultBuilder, BuildResult};
/// use libcnb::data::launch::LaunchBuilder;
/// use libcnb::data::process_type;
/// use libcnb::data::launch::ProcessBuilder;
///
/// let simple: Result<BuildResult, ()> = BuildResultBuilder::new().build();
///
/// let with_launch: Result<BuildResult, ()> = BuildResultBuilder::new()
///     .launch(
///         LaunchBuilder::new()
///             .process(
///                 ProcessBuilder::new(process_type!("type"), "command")
///                     .arg("-v")
///                     .build(),
///             )
///             .build(),
///     )
///     .build();
/// ```
#[derive(Default)]
#[must_use]
pub struct BuildResultBuilder {
    launch: Option<Launch>,
    store: Option<Store>,
    build_sboms: Vec<Sbom>,
    launch_sboms: Vec<Sbom>,
}

impl BuildResultBuilder {
    pub fn new() -> Self {
        Self::default()
    }

    /// Builds the final [`BuildResult`].
    ///
    /// This method returns the [`BuildResult`] wrapped in a [`Result`] even though its technically
    /// not fallible. This is done to simplify using this method in the context it's most often used
    /// in: a buildpack's [build method](crate::Buildpack::build).
    ///
    /// See [`build_unwrapped`](Self::build_unwrapped) for an unwrapped version of this method.
    pub fn build<E>(self) -> Result<BuildResult, E> {
        Ok(self.build_unwrapped())
    }

    pub fn build_unwrapped(self) -> BuildResult {
        BuildResult(InnerBuildResult::Pass {
            launch: self.launch,
            store: self.store,
            build_sboms: self.build_sboms,
            launch_sboms: self.launch_sboms,
        })
    }

    pub fn launch(mut self, launch: Launch) -> Self {
        self.launch = Some(launch);
        self
    }

    pub fn store<S: Into<Store>>(mut self, store: S) -> Self {
        self.store = Some(store.into());
        self
    }

    /// Adds a build SBOM to the build result.
    ///
    /// Entries in this SBOM represent materials in the build container for auditing purposes.
    /// This function can be called multiple times to add SBOMs in different formats.
    ///
    /// Please note that these SBOMs are not added to the resulting image, they are purely for
    /// auditing the build container.
    pub fn build_sbom(mut self, sbom: Sbom) -> Self {
        self.build_sboms.push(sbom);
        self
    }

    /// Adds a launch SBOM to the build result.
    ///
    /// Entries in this SBOM represent materials in the launch image for auditing purposes.
    /// This function can be called multiple times to add SBOMs in different formats.
    pub fn launch_sbom(mut self, sbom: Sbom) -> Self {
        self.launch_sboms.push(sbom);
        self
    }
}