From 44a01a9f4bb8578d73aed29e7dc60ca345d01f3a Mon Sep 17 00:00:00 2001 From: Manuel Fuchs Date: Tue, 12 Mar 2024 11:27:22 +0100 Subject: [PATCH 01/29] Add struct layer API --- libcnb/src/build.rs | 55 +++++++- libcnb/src/error.rs | 4 + libcnb/src/layer/handling.rs | 56 ++++++-- libcnb/src/layer/mod.rs | 2 + libcnb/src/layer/public_interface.rs | 4 +- libcnb/src/layer/struct_api/execute.rs | 167 ++++++++++++++++++++++++ libcnb/src/layer/struct_api/mod.rs | 173 +++++++++++++++++++++++++ 7 files changed, 449 insertions(+), 12 deletions(-) create mode 100644 libcnb/src/layer/struct_api/execute.rs create mode 100644 libcnb/src/layer/struct_api/mod.rs diff --git a/libcnb/src/build.rs b/libcnb/src/build.rs index 01221bf6..dc347a24 100644 --- a/libcnb/src/build.rs +++ b/libcnb/src/build.rs @@ -6,9 +6,17 @@ use crate::data::store::Store; use crate::data::{ buildpack::ComponentBuildpackDescriptor, buildpack_plan::BuildpackPlan, launch::Launch, }; -use crate::layer::{HandleLayerErrorOrBuildpackError, Layer, LayerData}; +use crate::layer::{ + CachedLayerDefinition, HandleLayerErrorOrBuildpackError, InspectExistingAction, IntoAction, + InvalidMetadataAction, Layer, LayerData, LayerRef, UncachedLayerDefinition, +}; use crate::sbom::Sbom; use crate::Target; +use libcnb_data::generic::GenericMetadata; +use libcnb_data::layer_content_metadata::LayerTypes; +use serde::de::DeserializeOwned; +use serde::Serialize; +use std::borrow::Borrow; use std::path::PathBuf; /// Context for the build phase execution. @@ -107,6 +115,51 @@ impl BuildContext { HandleLayerErrorOrBuildpackError::BuildpackError(e) => crate::Error::BuildpackError(e), }) } + + pub fn uncached_layer( + &self, + layer_name: LayerName, + layer_definition: impl Borrow, + ) -> crate::Result, B::Error> { + let layer_definition = layer_definition.borrow(); + + crate::layer::execute( + LayerTypes { + launch: layer_definition.launch, + build: layer_definition.build, + cache: false, + }, + &|_| InvalidMetadataAction::DeleteLayer, + &|_: &GenericMetadata, _| InspectExistingAction::Delete, + layer_name, + &self.layers_dir, + ) + } + + pub fn cached_layer<'a, M, X, Y, O, I>( + &self, + layer_name: LayerName, + layer_definition: impl Borrow>, + ) -> crate::Result, B::Error> + where + M: 'a + Serialize + DeserializeOwned, + O: 'a + IntoAction, X, B::Error>, + I: 'a + IntoAction, + { + let layer_definition = layer_definition.borrow(); + + crate::layer::execute( + LayerTypes { + launch: layer_definition.launch, + build: layer_definition.build, + cache: true, + }, + layer_definition.invalid_metadata, + layer_definition.inspect_existing, + layer_name, + &self.layers_dir, + ) + } } /// Describes the result of the build phase. diff --git a/libcnb/src/error.rs b/libcnb/src/error.rs index bc0ffd37..965d8bb5 100644 --- a/libcnb/src/error.rs +++ b/libcnb/src/error.rs @@ -1,4 +1,5 @@ use crate::data::launch::ProcessTypeError; +use crate::layer::execute::ExecuteLayerDefinitionError; use crate::layer::HandleLayerError; use libcnb_common::toml_file::TomlFileError; use std::fmt::Debug; @@ -14,6 +15,9 @@ pub enum Error { #[error("HandleLayer error: {0}")] HandleLayerError(#[from] HandleLayerError), + #[error("ExecuteLayerDefinitionError error: {0}")] + ExecuteLayerDefinitionError(#[from] ExecuteLayerDefinitionError), + #[error("Process type error: {0}")] ProcessTypeError(#[from] ProcessTypeError), diff --git a/libcnb/src/layer/handling.rs b/libcnb/src/layer/handling.rs index 39538d43..fa4bc5f9 100644 --- a/libcnb/src/layer/handling.rs +++ b/libcnb/src/layer/handling.rs @@ -1,16 +1,19 @@ // This lint triggers when both layer_dir and layers_dir are present which are quite common. #![allow(clippy::similar_names)] +use super::public_interface::Layer; use crate::build::BuildContext; use crate::data::layer::LayerName; use crate::data::layer_content_metadata::LayerContentMetadata; use crate::generic::GenericMetadata; -use crate::layer::{ExistingLayerStrategy, Layer, LayerData, MetadataMigration}; +use crate::layer::{ExistingLayerStrategy, LayerData, MetadataMigration}; use crate::layer_env::LayerEnv; use crate::sbom::{cnb_sbom_path, Sbom}; use crate::util::{default_on_not_found, remove_dir_recursively}; use crate::Buildpack; use crate::{write_toml_file, TomlFileError}; +use libcnb_common::toml_file::read_toml_file; +use libcnb_data::layer_content_metadata::LayerTypes; use libcnb_data::sbom::SBOM_FORMATS; use serde::de::DeserializeOwned; use serde::Serialize; @@ -279,19 +282,19 @@ pub enum ReplaceLayerSbomsError { } #[derive(Debug)] -enum ExecDPrograms { +pub(in crate::layer) enum ExecDPrograms { Keep, Replace(HashMap), } #[derive(Debug)] -enum Sboms { +pub(in crate::layer) enum Sboms { Keep, Replace(Vec), } /// Does not error if the layer doesn't exist. -fn delete_layer>( +pub(in crate::layer) fn delete_layer>( layers_dir: P, layer_name: &LayerName, ) -> Result<(), DeleteLayerError> { @@ -304,7 +307,7 @@ fn delete_layer>( Ok(()) } -fn replace_layer_sboms>( +pub(in crate::layer) fn replace_layer_sboms>( layers_dir: P, layer_name: &LayerName, sboms: &[Sbom], @@ -331,7 +334,7 @@ fn replace_layer_sboms>( Ok(()) } -fn replace_layer_exec_d_programs>( +pub(in crate::layer) fn replace_layer_exec_d_programs>( layers_dir: P, layer_name: &LayerName, exec_d_programs: &HashMap, @@ -372,7 +375,7 @@ fn replace_layer_exec_d_programs>( Ok(()) } -fn write_layer_metadata>( +pub(in crate::layer) fn write_layer_metadata>( layers_dir: P, layer_name: &LayerName, layer_content_metadata: &LayerContentMetadata, @@ -387,7 +390,7 @@ fn write_layer_metadata>( } /// Updates layer metadata on disk -fn write_layer>( +pub(in crate::layer) fn write_layer>( layers_dir: P, layer_name: &LayerName, layer_env: &LayerEnv, @@ -413,7 +416,7 @@ fn write_layer>( Ok(()) } -fn read_layer>( +pub(crate) fn read_layer>( layers_dir: P, layer_name: &LayerName, ) -> Result>, ReadLayerError> { @@ -446,6 +449,7 @@ fn read_layer>( } let layer_toml_contents = fs::read_to_string(&layer_toml_path)?; + let layer_content_metadata = toml::from_str::>(&layer_toml_contents) .map_err(ReadLayerError::LayerContentMetadataParseError)?; @@ -459,6 +463,40 @@ fn read_layer>( })) } +pub(crate) fn replace_layer_types>( + layers_dir: P, + layer_name: &LayerName, + layer_types: LayerTypes, +) -> Result<(), WriteLayerMetadataError> { + let layer_content_metadata_path = layers_dir.as_ref().join(format!("{layer_name}.toml")); + + let mut content_metadata = + read_toml_file::(&layer_content_metadata_path)?; + content_metadata.types = Some(layer_types); + + write_toml_file(&content_metadata, &layer_content_metadata_path) + .map_err(WriteLayerMetadataError::TomlFileError) +} + +pub(crate) fn replace_layer_metadata>( + layers_dir: P, + layer_name: &LayerName, + metadata: M, +) -> Result<(), WriteLayerMetadataError> { + let layer_content_metadata_path = layers_dir.as_ref().join(format!("{layer_name}.toml")); + + let content_metadata = read_toml_file::(&layer_content_metadata_path)?; + + write_toml_file( + &LayerContentMetadata { + types: content_metadata.types, + metadata, + }, + &layer_content_metadata_path, + ) + .map_err(WriteLayerMetadataError::TomlFileError) +} + #[cfg(test)] mod tests { use super::*; diff --git a/libcnb/src/layer/mod.rs b/libcnb/src/layer/mod.rs index 29ab4e6b..f607fafc 100644 --- a/libcnb/src/layer/mod.rs +++ b/libcnb/src/layer/mod.rs @@ -3,8 +3,10 @@ mod handling; mod public_interface; +pub(crate) mod struct_api; #[cfg(test)] mod tests; pub(crate) use handling::*; pub use public_interface::*; +pub use struct_api::*; diff --git a/libcnb/src/layer/public_interface.rs b/libcnb/src/layer/public_interface.rs index c2d7a150..ad2c053f 100644 --- a/libcnb/src/layer/public_interface.rs +++ b/libcnb/src/layer/public_interface.rs @@ -255,8 +255,8 @@ impl LayerResultBuilder { /// /// This method returns the [`LayerResult`] wrapped in a [`Result`] even though its technically /// not fallible. This is done to simplify using this method in the contexts it's most often - /// used in: a layer's [create](crate::layer::Layer::create) and/or - /// [update](crate::layer::Layer::update) methods. + /// used in: a layer's [create](crate::layer::LayerRef::create) and/or + /// [update](crate::layer::LayerRef::update) methods. /// /// See [`build_unwrapped`](Self::build_unwrapped) for an unwrapped version of this method. pub fn build(self) -> Result, E> { diff --git a/libcnb/src/layer/struct_api/execute.rs b/libcnb/src/layer/struct_api/execute.rs new file mode 100644 index 00000000..28a5ae2b --- /dev/null +++ b/libcnb/src/layer/struct_api/execute.rs @@ -0,0 +1,167 @@ +use crate::layer::handling::{ExecDPrograms, Sboms}; +use crate::layer::{ + DeleteLayerError, EmptyReason, InspectExistingAction, IntoAction, InvalidMetadataAction, + LayerContents, LayerRef, ReadLayerError, WriteLayerError, +}; +use crate::layer_env::LayerEnv; +use crate::Buildpack; +use libcnb_common::toml_file::{read_toml_file, TomlFileError}; +use libcnb_data::generic::GenericMetadata; +use libcnb_data::layer::LayerName; +use libcnb_data::layer_content_metadata::{LayerContentMetadata, LayerTypes}; +use serde::de::DeserializeOwned; +use serde::Serialize; +use std::marker::PhantomData; +use std::path::{Path, PathBuf}; + +#[derive(thiserror::Error, Debug)] +#[allow(clippy::enum_variant_names)] +pub enum ExecuteLayerDefinitionError { + #[error("{0}")] + ReadLayerError(#[from] ReadLayerError), + #[error("{0}")] + WriteLayerError(#[from] WriteLayerError), + #[error("{0}")] + DeleteLayerError(#[from] DeleteLayerError), + #[error("Cannot read generic layer metadata: {0}")] + CouldNotReadGenericLayerMetadata(TomlFileError), + #[error("Cannot read layer {0} after creating it")] + CouldNotReadLayerAfterCreate(LayerName), +} + +#[allow(clippy::too_many_lines)] +pub(crate) fn execute( + layer_types: LayerTypes, + invalid_metadata: &dyn Fn(&GenericMetadata) -> MA, + inspect_existing: &dyn Fn(&M, &Path) -> IA, + layer_name: LayerName, + layers_dir: &Path, +) -> crate::Result, B::Error> +where + B: Buildpack + ?Sized, + M: Serialize + DeserializeOwned, + MA: IntoAction, MC, B::Error>, + IA: IntoAction, +{ + match crate::layer::handling::read_layer::(layers_dir, &layer_name) { + Ok(None) => create_layer(layer_types, &layer_name, layers_dir, EmptyReason::Uncached), + Ok(Some(layer_data)) => { + let inspect_action = + inspect_existing(&layer_data.content_metadata.metadata, &layer_data.path) + .into_action() + .map_err(crate::Error::BuildpackError)?; + + match inspect_action { + (InspectExistingAction::Delete, cause) => { + crate::layer::handling::delete_layer(layers_dir, &layer_name) + .map_err(ExecuteLayerDefinitionError::DeleteLayerError)?; + + create_layer( + layer_types, + &layer_name, + layers_dir, + EmptyReason::Inspect(cause), + ) + } + (InspectExistingAction::Keep, cause) => { + // Always write the layer types as: + // a) they might be different from what is currently on disk + // b) the cache field will be removed by CNB lifecycle on cache restore + crate::layer::replace_layer_types(layers_dir, &layer_name, layer_types) + .map_err(|error| { + ExecuteLayerDefinitionError::WriteLayerError( + WriteLayerError::WriteLayerMetadataError(error), + ) + })?; + + Ok(LayerRef { + name: layer_data.name, + layers_dir: PathBuf::from(layers_dir), + buildpack: PhantomData, + contents: LayerContents::Cached(cause), + }) + } + } + } + Err(ReadLayerError::LayerContentMetadataParseError(_)) => { + let layer_content_metadata = read_toml_file::( + layers_dir.join(format!("{}.toml", &layer_name)), + ) + .map_err(ExecuteLayerDefinitionError::CouldNotReadGenericLayerMetadata)?; + + let invalid_metadata_action = invalid_metadata(&layer_content_metadata.metadata) + .into_action() + .map_err(crate::Error::BuildpackError)?; + + match invalid_metadata_action { + (InvalidMetadataAction::DeleteLayer, cause) => { + crate::layer::handling::delete_layer(layers_dir, &layer_name) + .map_err(ExecuteLayerDefinitionError::DeleteLayerError)?; + + create_layer( + layer_types, + &layer_name, + layers_dir, + EmptyReason::MetadataInvalid(cause), + ) + } + (InvalidMetadataAction::ReplaceMetadata(metadata), _) => { + crate::layer::replace_layer_metadata(layers_dir, &layer_name, metadata) + .map_err(|error| { + ExecuteLayerDefinitionError::WriteLayerError( + WriteLayerError::WriteLayerMetadataError(error), + ) + })?; + + execute( + layer_types, + invalid_metadata, + inspect_existing, + layer_name, + layers_dir, + ) + } + } + } + Err(read_layer_error) => Err(ExecuteLayerDefinitionError::ReadLayerError( + read_layer_error, + ))?, + } +} + +fn create_layer( + layer_types: LayerTypes, + layer_name: &LayerName, + layers_dir: &Path, + empty_reason: EmptyReason, +) -> Result, crate::Error> +where + B: Buildpack + ?Sized, +{ + crate::layer::handling::write_layer( + layers_dir, + layer_name, + &LayerEnv::new(), + &LayerContentMetadata { + types: Some(layer_types), + metadata: GenericMetadata::default(), + }, + ExecDPrograms::Keep, + Sboms::Keep, + ) + .map_err(ExecuteLayerDefinitionError::WriteLayerError)?; + + let layer_data = + crate::layer::handling::read_layer::(layers_dir, layer_name) + .map_err(ExecuteLayerDefinitionError::ReadLayerError)? + .ok_or(ExecuteLayerDefinitionError::CouldNotReadLayerAfterCreate( + layer_name.clone(), + ))?; + + Ok(LayerRef { + name: layer_data.name, + layers_dir: PathBuf::from(layers_dir), + buildpack: PhantomData, + contents: LayerContents::Empty(empty_reason), + }) +} diff --git a/libcnb/src/layer/struct_api/mod.rs b/libcnb/src/layer/struct_api/mod.rs new file mode 100644 index 00000000..f1529f54 --- /dev/null +++ b/libcnb/src/layer/struct_api/mod.rs @@ -0,0 +1,173 @@ +pub(crate) mod execute; + +use crate::layer::handling::{replace_layer_exec_d_programs, replace_layer_sboms}; +use crate::layer::{HandleLayerError, WriteLayerError}; +use crate::layer_env::LayerEnv; +use crate::sbom::Sbom; +use crate::Buildpack; +pub(crate) use execute::execute; +use libcnb_data::generic::GenericMetadata; +use libcnb_data::layer::LayerName; +use serde::Serialize; +use std::collections::HashMap; +use std::marker::PhantomData; +use std::path::{Path, PathBuf}; + +/// A definition for a cached layer. +pub struct CachedLayerDefinition<'a, M, MA, EA> { + /// Whether the layer is intended for build. + pub build: bool, + /// Whether the layer is intended for launch. + pub launch: bool, + /// Callback for when the metadata of an existing layer cannot be parsed as `M`. + /// + /// Allows replacing the metadata before continuing (i.e. migration to a newer version) or + /// deleting the layer. + pub invalid_metadata: &'a dyn Fn(&GenericMetadata) -> MA, + /// Callback when the layer already exists to validate the contents and metadata. Can be used + /// to delete existing cached layers. + pub inspect_existing: &'a dyn Fn(&M, &Path) -> EA, +} + +/// A definition for an uncached layer. +pub struct UncachedLayerDefinition { + /// Whether the layer is intended for build. + pub build: bool, + /// Whether the layer is intended for launch. + pub launch: bool, +} + +/// The action to take when the layer metadata is invalid. +#[derive(Copy, Clone)] +pub enum InvalidMetadataAction { + DeleteLayer, + ReplaceMetadata(M), +} + +/// The action to take after inspecting existing layer data. +#[derive(Copy, Clone)] +pub enum InspectExistingAction { + Delete, + Keep, +} + +pub enum LayerContents { + /// The layer contains validated cached contents from a previous buildpack run. + /// + /// See: `inspect_existing` in [`CachedLayerDefinition`]. + Cached(Y), + /// The layer is empty. Inspect the contained [`EmptyReason`] for details why. + Empty(EmptyReason), +} + +pub enum EmptyReason { + /// The layer wasn't cached in a previous buildpack run. + Uncached, + /// The layer was cached in a previous buildpack run, but the metadata was invalid and couldn't + /// be converted into a valid form. Subsequently, the layer was deleted entirely. + /// + /// See: `invalid_metadata` in [`CachedLayerDefinition`]. + MetadataInvalid(X), + /// The layer was cached in a previous buildpack run, but the `inspect_existing` function + /// rejected the contents. + /// + /// See: `inspect_existing` in [`CachedLayerDefinition`]. + Inspect(Y), +} + +/// A value-to-value conversion for layer actions. +/// +/// Similar to [`Into`], but specialized. Allowing it to also be implemented for +/// values in the standard library such as [`Result`]. +pub trait IntoAction { + fn into_action(self) -> Result<(T, C), E>; +} + +impl IntoAction for T { + fn into_action(self) -> Result<(T, ()), E> { + Ok((self, ())) + } +} + +impl IntoAction for (T, C) { + fn into_action(self) -> Result<(T, C), E> { + Ok(self) + } +} + +impl IntoAction for Result<(T, C), E> { + fn into_action(self) -> Result<(T, C), E> { + self + } +} + +impl IntoAction for Result { + fn into_action(self) -> Result<(T, ()), E> { + self.map(|value| (value, ())) + } +} + +pub struct LayerRef +where + B: Buildpack + ?Sized, +{ + name: LayerName, + layers_dir: PathBuf, + buildpack: PhantomData, + pub contents: LayerContents, +} + +impl LayerRef +where + B: Buildpack, +{ + pub fn path(&self) -> PathBuf { + self.layers_dir.join(self.name.as_str()) + } + + pub fn replace_metadata(&self, metadata: M) -> crate::Result<(), B::Error> + where + M: Serialize, + { + crate::layer::replace_layer_metadata(&self.layers_dir, &self.name, metadata).map_err( + |error| { + crate::Error::HandleLayerError(HandleLayerError::WriteLayerError( + WriteLayerError::WriteLayerMetadataError(error), + )) + }, + ) + } + + pub fn replace_env(&self, env: LayerEnv) -> crate::Result<(), B::Error> { + env.write_to_layer_dir(self.path()).map_err(|a| { + crate::Error::HandleLayerError(HandleLayerError::WriteLayerError( + WriteLayerError::WriteLayerEnvError(a), + )) + }) + } + + pub fn replace_sboms(&self, sboms: &[Sbom]) -> crate::Result<(), B::Error> { + replace_layer_sboms(&self.layers_dir, &self.name, sboms).map_err(|error| { + crate::Error::HandleLayerError(HandleLayerError::WriteLayerError( + WriteLayerError::ReplaceLayerSbomsError(error), + )) + }) + } + + pub fn replace_exec_d_programs(&self, programs: P) -> crate::Result<(), B::Error> + where + S: Into, + P: IntoIterator, + { + let programs = programs + .into_iter() + .map(|(k, v)| (k.into(), v)) + .collect::>(); + + replace_layer_exec_d_programs(&self.layers_dir, &self.name, &programs).map_err(|error| { + crate::Error::HandleLayerError(HandleLayerError::WriteLayerError( + WriteLayerError::ReplaceLayerExecdProgramsError(error), + )) + }) + } +} From 0d93533876144c07f68617494dfc84a5969a026e Mon Sep 17 00:00:00 2001 From: Manuel Fuchs Date: Tue, 12 Mar 2024 16:43:37 +0100 Subject: [PATCH 02/29] Add deprecation annotations --- libcnb/src/build.rs | 1 + libcnb/src/layer/public_interface.rs | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/libcnb/src/build.rs b/libcnb/src/build.rs index dc347a24..72846a05 100644 --- a/libcnb/src/build.rs +++ b/libcnb/src/build.rs @@ -103,6 +103,7 @@ impl BuildContext { /// } /// } /// ``` + #[deprecated = "The Layer trait API was replaced by LayerDefinitions. Use `cached_layer` and `uncached_layer`."] pub fn handle_layer>( &self, layer_name: LayerName, diff --git a/libcnb/src/layer/public_interface.rs b/libcnb/src/layer/public_interface.rs index ad2c053f..2fbbbd96 100644 --- a/libcnb/src/layer/public_interface.rs +++ b/libcnb/src/layer/public_interface.rs @@ -16,6 +16,7 @@ use std::path::{Path, PathBuf}; /// depending on its state. To use a `Layer` implementation during build, use /// [`BuildContext::handle_layer`](crate::build::BuildContext::handle_layer). #[allow(unused_variables)] +#[deprecated = "The Layer trait API was replaced by LayerDefinitions and will be removed soon."] pub trait Layer { /// The buildpack this layer is used with. type Buildpack: Buildpack; @@ -135,6 +136,7 @@ pub trait Layer { /// The result of a [`Layer::existing_layer_strategy`] call. #[derive(Eq, PartialEq, Clone, Copy, Debug)] +#[deprecated = "The Layer trait API was replaced by LayerDefinitions and will be removed soon."] pub enum ExistingLayerStrategy { /// The existing layer should not be modified. Keep, @@ -145,6 +147,7 @@ pub enum ExistingLayerStrategy { } /// The result of a [`Layer::migrate_incompatible_metadata`] call. +#[deprecated = "The Layer trait API was replaced by LayerDefinitions and will be removed soon."] pub enum MetadataMigration { /// The layer should be recreated entirely. RecreateLayer, @@ -153,6 +156,7 @@ pub enum MetadataMigration { } /// Information about an existing CNB layer. +#[deprecated = "The Layer trait API was replaced by LayerDefinitions and will be removed soon."] pub struct LayerData { pub name: LayerName, /// The layer's path, should not be modified outside of a [`Layer`] implementation. @@ -165,6 +169,7 @@ pub struct LayerData { /// /// Essentially, this carries additional metadata about a layer this later persisted according /// to the CNB spec by libcnb. +#[deprecated = "The Layer trait API was replaced by LayerDefinitions and will be removed soon."] pub struct LayerResult { pub metadata: M, pub env: Option, @@ -173,6 +178,7 @@ pub struct LayerResult { } /// A builder that simplifies the creation of [`LayerResult`] values. +#[deprecated = "The Layer trait API was replaced by LayerDefinitions and will be removed soon."] pub struct LayerResultBuilder { metadata: M, env: Option, From ec9067d1649d5c2584d9a66fcdab24a1ca520d4a Mon Sep 17 00:00:00 2001 From: Manuel Fuchs Date: Wed, 13 Mar 2024 19:39:59 +0100 Subject: [PATCH 03/29] Move existing layer API to separate module, move shared code --- libcnb/src/layer/{ => layer_api}/handling.rs | 132 ++------------- .../{public_interface.rs => layer_api/mod.rs} | 4 + libcnb/src/layer/{ => layer_api}/tests.rs | 4 +- libcnb/src/layer/mod.rs | 12 +- libcnb/src/layer/shared.rs | 155 ++++++++++++++++++ libcnb/src/layer/struct_api/execute.rs | 60 +++---- 6 files changed, 210 insertions(+), 157 deletions(-) rename libcnb/src/layer/{ => layer_api}/handling.rs (87%) rename libcnb/src/layer/{public_interface.rs => layer_api/mod.rs} (99%) rename libcnb/src/layer/{ => layer_api}/tests.rs (99%) create mode 100644 libcnb/src/layer/shared.rs diff --git a/libcnb/src/layer/handling.rs b/libcnb/src/layer/layer_api/handling.rs similarity index 87% rename from libcnb/src/layer/handling.rs rename to libcnb/src/layer/layer_api/handling.rs index fa4bc5f9..c95f62c6 100644 --- a/libcnb/src/layer/handling.rs +++ b/libcnb/src/layer/layer_api/handling.rs @@ -1,11 +1,14 @@ // This lint triggers when both layer_dir and layers_dir are present which are quite common. #![allow(clippy::similar_names)] -use super::public_interface::Layer; +use super::Layer; use crate::build::BuildContext; use crate::data::layer::LayerName; use crate::data::layer_content_metadata::LayerContentMetadata; use crate::generic::GenericMetadata; +use crate::layer::shared::{ + delete_layer, DeleteLayerError, ReadLayerError, WriteLayerMetadataError, +}; use crate::layer::{ExistingLayerStrategy, LayerData, MetadataMigration}; use crate::layer_env::LayerEnv; use crate::sbom::{cnb_sbom_path, Sbom}; @@ -220,21 +223,6 @@ pub enum HandleLayerError { UnexpectedMissingLayer, } -#[derive(thiserror::Error, Debug)] -pub enum DeleteLayerError { - #[error("I/O error while deleting existing layer: {0}")] - IoError(#[from] std::io::Error), -} - -#[derive(thiserror::Error, Debug)] -pub enum ReadLayerError { - #[error("Layer content metadata couldn't be parsed!")] - LayerContentMetadataParseError(toml::de::Error), - - #[error("Unexpected I/O error while reading layer: {0}")] - IoError(#[from] std::io::Error), -} - #[derive(thiserror::Error, Debug)] #[allow(clippy::enum_variant_names)] pub enum WriteLayerError { @@ -251,15 +239,6 @@ pub enum WriteLayerError { ReplaceLayerSbomsError(#[from] ReplaceLayerSbomsError), } -#[derive(thiserror::Error, Debug)] -pub enum WriteLayerMetadataError { - #[error("Unexpected I/O error while writing layer metadata: {0}")] - IoError(#[from] std::io::Error), - - #[error("Error while writing layer content metadata TOML: {0}")] - TomlFileError(#[from] TomlFileError), -} - #[derive(thiserror::Error, Debug)] pub enum ReplaceLayerExecdProgramsError { #[error("Unexpected I/O error while replacing layer execd programs: {0}")] @@ -293,20 +272,6 @@ pub(in crate::layer) enum Sboms { Replace(Vec), } -/// Does not error if the layer doesn't exist. -pub(in crate::layer) fn delete_layer>( - layers_dir: P, - layer_name: &LayerName, -) -> Result<(), DeleteLayerError> { - let layer_dir = layers_dir.as_ref().join(layer_name.as_str()); - let layer_toml = layers_dir.as_ref().join(format!("{layer_name}.toml")); - - default_on_not_found(remove_dir_recursively(&layer_dir))?; - default_on_not_found(fs::remove_file(layer_toml))?; - - Ok(()) -} - pub(in crate::layer) fn replace_layer_sboms>( layers_dir: P, layer_name: &LayerName, @@ -420,81 +385,20 @@ pub(crate) fn read_layer>( layers_dir: P, layer_name: &LayerName, ) -> Result>, ReadLayerError> { - let layer_dir_path = layers_dir.as_ref().join(layer_name.as_str()); - let layer_toml_path = layers_dir.as_ref().join(format!("{layer_name}.toml")); - - if !layer_dir_path.exists() && !layer_toml_path.exists() { - return Ok(None); - } else if !layer_dir_path.exists() && layer_toml_path.exists() { - // This is a valid case according to the spec: - // https://github.com/buildpacks/spec/blob/7b20dfa070ed428c013e61a3cefea29030af1732/buildpack.md#layer-types - // - // When launch = true, build = false, cache = false, the layer metadata will be restored but - // not the layer itself. However, we choose to not support this case as of now. It would - // complicate the API we need to expose to the user of libcnb as this case is very different - // compared to all other combinations of launch, build and cache. It's the only case where - // a cache = false layer restores some of its data between builds. - // - // To normalize, we remove the layer TOML file and treat the layer as non-existent. - fs::remove_file(&layer_toml_path)?; - return Ok(None); - } - - // An empty layer content metadata file is valid and the CNB spec is not clear if the lifecycle - // has to restore them if they're empty. This is especially important since the layer types - // are removed from the file if it's restored. To normalize, we write an empty file if the layer - // directory exists without the metadata file. - if !layer_toml_path.exists() { - fs::write(&layer_toml_path, "")?; - } - - let layer_toml_contents = fs::read_to_string(&layer_toml_path)?; - - let layer_content_metadata = toml::from_str::>(&layer_toml_contents) - .map_err(ReadLayerError::LayerContentMetadataParseError)?; - - let layer_env = LayerEnv::read_from_layer_dir(&layer_dir_path)?; - - Ok(Some(LayerData { - name: layer_name.clone(), - path: layer_dir_path, - env: layer_env, - content_metadata: layer_content_metadata, - })) -} - -pub(crate) fn replace_layer_types>( - layers_dir: P, - layer_name: &LayerName, - layer_types: LayerTypes, -) -> Result<(), WriteLayerMetadataError> { - let layer_content_metadata_path = layers_dir.as_ref().join(format!("{layer_name}.toml")); - - let mut content_metadata = - read_toml_file::(&layer_content_metadata_path)?; - content_metadata.types = Some(layer_types); - - write_toml_file(&content_metadata, &layer_content_metadata_path) - .map_err(WriteLayerMetadataError::TomlFileError) -} - -pub(crate) fn replace_layer_metadata>( - layers_dir: P, - layer_name: &LayerName, - metadata: M, -) -> Result<(), WriteLayerMetadataError> { - let layer_content_metadata_path = layers_dir.as_ref().join(format!("{layer_name}.toml")); - - let content_metadata = read_toml_file::(&layer_content_metadata_path)?; - - write_toml_file( - &LayerContentMetadata { - types: content_metadata.types, - metadata, - }, - &layer_content_metadata_path, - ) - .map_err(WriteLayerMetadataError::TomlFileError) + crate::layer::shared::read_layer(layers_dir, layer_name).map(|read_layer| { + read_layer.map(|read_layer| { + + LayerEnv::read_from_layer_dir(&read_layer.path).map_err(ReadLayerError::IoError) + + + LayerEnv::read_from_layer_dir(&read_layer.path).map(|layer_env| LayerData { + name: read_layer.name, + path: read_layer.path, + content_metadata: read_layer.metadata, + env: layer_env, + }) + }) + }) } #[cfg(test)] diff --git a/libcnb/src/layer/public_interface.rs b/libcnb/src/layer/layer_api/mod.rs similarity index 99% rename from libcnb/src/layer/public_interface.rs rename to libcnb/src/layer/layer_api/mod.rs index 2fbbbd96..4117656f 100644 --- a/libcnb/src/layer/public_interface.rs +++ b/libcnb/src/layer/layer_api/mod.rs @@ -10,6 +10,10 @@ use serde::Serialize; use std::collections::HashMap; use std::path::{Path, PathBuf}; +mod handling; +#[cfg(test)] +mod tests; + /// Represents a buildpack layer written with the libcnb framework. /// /// Buildpack authors implement this trait to define how a layer is created/updated/removed diff --git a/libcnb/src/layer/tests.rs b/libcnb/src/layer/layer_api/tests.rs similarity index 99% rename from libcnb/src/layer/tests.rs rename to libcnb/src/layer/layer_api/tests.rs index 9b99d39e..a54cd8e1 100644 --- a/libcnb/src/layer/tests.rs +++ b/libcnb/src/layer/layer_api/tests.rs @@ -14,9 +14,9 @@ use crate::data::buildpack_id; use crate::data::layer_content_metadata::LayerTypes; use crate::detect::{DetectContext, DetectResult, DetectResultBuilder}; use crate::generic::{GenericMetadata, GenericPlatform}; +use crate::layer::layer_api::handling::handle_layer; use crate::layer::{ - handle_layer, ExistingLayerStrategy, Layer, LayerData, LayerResult, LayerResultBuilder, - MetadataMigration, + ExistingLayerStrategy, Layer, LayerData, LayerResult, LayerResultBuilder, MetadataMigration, }; use crate::layer_env::{LayerEnv, ModificationBehavior, Scope}; use crate::{read_toml_file, Buildpack, Env, Target, LIBCNB_SUPPORTED_BUILDPACK_API}; diff --git a/libcnb/src/layer/mod.rs b/libcnb/src/layer/mod.rs index f607fafc..2f365702 100644 --- a/libcnb/src/layer/mod.rs +++ b/libcnb/src/layer/mod.rs @@ -1,12 +1,8 @@ //! Provides types and helpers to work with layers. -mod handling; -mod public_interface; +mod layer_api; +pub(in crate::layer) mod shared; +mod struct_api; -pub(crate) mod struct_api; -#[cfg(test)] -mod tests; - -pub(crate) use handling::*; -pub use public_interface::*; +pub use layer_api::*; pub use struct_api::*; diff --git a/libcnb/src/layer/shared.rs b/libcnb/src/layer/shared.rs new file mode 100644 index 00000000..c6d5d412 --- /dev/null +++ b/libcnb/src/layer/shared.rs @@ -0,0 +1,155 @@ +// This lint triggers when both layer_dir and layers_dir are present which are quite common. +#![allow(clippy::similar_names)] + +use crate::util::{default_on_not_found, remove_dir_recursively}; +use libcnb_common::toml_file::{read_toml_file, write_toml_file, TomlFileError}; +use libcnb_data::layer::LayerName; +use libcnb_data::layer_content_metadata::{LayerContentMetadata, LayerTypes}; +use serde::de::DeserializeOwned; +use serde::Serialize; +use std::fs; +use std::path::{Path, PathBuf}; + +pub(in crate::layer) fn read_layer>( + layers_dir: P, + layer_name: &LayerName, +) -> Result>, ReadLayerError> { + let layer_dir_path = layers_dir.as_ref().join(layer_name.as_str()); + let layer_toml_path = layers_dir.as_ref().join(format!("{layer_name}.toml")); + + if !layer_dir_path.exists() && !layer_toml_path.exists() { + return Ok(None); + } else if !layer_dir_path.exists() && layer_toml_path.exists() { + // This is a valid case according to the spec: + // https://github.com/buildpacks/spec/blob/7b20dfa070ed428c013e61a3cefea29030af1732/buildpack.md#layer-types + // + // When launch = true, build = false, cache = false, the layer metadata will be restored but + // not the layer itself. However, we choose to not support this case as of now. It would + // complicate the API we need to expose to the user of libcnb as this case is very different + // compared to all other combinations of launch, build and cache. It's the only case where + // a cache = false layer restores some of its data between builds. + // + // To normalize, we remove the layer TOML file and treat the layer as non-existent. + fs::remove_file(&layer_toml_path)?; + return Ok(None); + } + + // An empty layer content metadata file is valid and the CNB spec is not clear if the lifecycle + // has to restore them if they're empty. This is especially important since the layer types + // are removed from the file if it's restored. To normalize, we write an empty file if the layer + // directory exists without the metadata file. + if !layer_toml_path.exists() { + fs::write(&layer_toml_path, "")?; + } + + let layer_toml_contents = fs::read_to_string(&layer_toml_path)?; + + let layer_content_metadata = toml::from_str::>(&layer_toml_contents) + .map_err(ReadLayerError::LayerContentMetadataParseError)?; + + Ok(Some(ReadLayer { + name: layer_name.clone(), + path: layer_dir_path, + metadata: layer_content_metadata, + })) +} + +pub(in crate::layer) struct ReadLayer { + pub(in crate::layer) name: LayerName, + pub(in crate::layer) path: PathBuf, + pub(in crate::layer) metadata: LayerContentMetadata, +} + +#[derive(thiserror::Error, Debug)] +pub enum ReadLayerError { + #[error("Layer content metadata couldn't be parsed!")] + LayerContentMetadataParseError(toml::de::Error), + + #[error("Unexpected I/O error while reading layer: {0}")] + IoError(#[from] std::io::Error), +} + +/// Updates layer metadata on disk +pub(in crate::layer) fn write_layer>( + layers_dir: P, + layer_name: &LayerName, + layer_content_metadata: &LayerContentMetadata, +) -> Result<(), WriteLayerError> { + let layers_dir = layers_dir.as_ref(); + fs::create_dir_all(layers_dir.join(layer_name.as_str()))?; + replace_layer_metadata(layers_dir, layer_name, layer_content_metadata)?; + + Ok(()) +} + +#[derive(thiserror::Error, Debug)] +pub enum WriteLayerError { + #[error("Layer content metadata couldn't be parsed!")] + WriteLayerMetadataError(WriteLayerMetadataError), + + #[error("Unexpected I/O error while writing layer: {0}")] + IoError(#[from] std::io::Error), +} + +/// Does not error if the layer doesn't exist. +pub(in crate::layer) fn delete_layer>( + layers_dir: P, + layer_name: &LayerName, +) -> Result<(), DeleteLayerError> { + let layer_dir = layers_dir.as_ref().join(layer_name.as_str()); + let layer_toml = layers_dir.as_ref().join(format!("{layer_name}.toml")); + + default_on_not_found(remove_dir_recursively(&layer_dir))?; + default_on_not_found(fs::remove_file(layer_toml))?; + + Ok(()) +} + +#[derive(thiserror::Error, Debug)] +pub enum DeleteLayerError { + #[error("I/O error while deleting existing layer: {0}")] + IoError(#[from] std::io::Error), +} + +pub(in crate::layer) fn replace_layer_metadata>( + layers_dir: P, + layer_name: &LayerName, + metadata: M, +) -> Result<(), WriteLayerMetadataError> { + let layer_content_metadata_path = layers_dir.as_ref().join(format!("{layer_name}.toml")); + + let content_metadata = read_toml_file::(&layer_content_metadata_path)?; + + write_toml_file( + &LayerContentMetadata { + types: content_metadata.types, + metadata, + }, + &layer_content_metadata_path, + ) + .map_err(WriteLayerMetadataError::TomlFileError) +} + +pub(crate) fn replace_layer_types>( + layers_dir: P, + layer_name: &LayerName, + layer_types: LayerTypes, +) -> Result<(), WriteLayerMetadataError> { + let layer_content_metadata_path = layers_dir.as_ref().join(format!("{layer_name}.toml")); + + let mut content_metadata = + read_toml_file::(&layer_content_metadata_path)?; + content_metadata.types = Some(layer_types); + + write_toml_file(&content_metadata, &layer_content_metadata_path) + .map_err(WriteLayerMetadataError::TomlFileError) +} + +#[derive(thiserror::Error, Debug)] +pub enum WriteLayerMetadataError { + #[error("Unexpected I/O error while writing layer metadata: {0}")] + IoError(#[from] std::io::Error), + + #[error("Error while writing layer content metadata TOML: {0}")] + TomlFileError(#[from] TomlFileError), +} diff --git a/libcnb/src/layer/struct_api/execute.rs b/libcnb/src/layer/struct_api/execute.rs index 28a5ae2b..e8e0ba36 100644 --- a/libcnb/src/layer/struct_api/execute.rs +++ b/libcnb/src/layer/struct_api/execute.rs @@ -1,9 +1,10 @@ -use crate::layer::handling::{ExecDPrograms, Sboms}; +use crate::layer::shared::{ + delete_layer, read_layer, replace_layer_metadata, replace_layer_types, DeleteLayerError, + ReadLayerError, WriteLayerError, +}; use crate::layer::{ - DeleteLayerError, EmptyReason, InspectExistingAction, IntoAction, InvalidMetadataAction, - LayerContents, LayerRef, ReadLayerError, WriteLayerError, + EmptyReason, InspectExistingAction, IntoAction, InvalidMetadataAction, LayerContents, LayerRef, }; -use crate::layer_env::LayerEnv; use crate::Buildpack; use libcnb_common::toml_file::{read_toml_file, TomlFileError}; use libcnb_data::generic::GenericMetadata; @@ -43,17 +44,16 @@ where MA: IntoAction, MC, B::Error>, IA: IntoAction, { - match crate::layer::handling::read_layer::(layers_dir, &layer_name) { + match read_layer::(layers_dir, &layer_name) { Ok(None) => create_layer(layer_types, &layer_name, layers_dir, EmptyReason::Uncached), Ok(Some(layer_data)) => { - let inspect_action = - inspect_existing(&layer_data.content_metadata.metadata, &layer_data.path) - .into_action() - .map_err(crate::Error::BuildpackError)?; + let inspect_action = inspect_existing(&layer_data.metadata.metadata, &layer_data.path) + .into_action() + .map_err(crate::Error::BuildpackError)?; match inspect_action { (InspectExistingAction::Delete, cause) => { - crate::layer::handling::delete_layer(layers_dir, &layer_name) + delete_layer(layers_dir, &layer_name) .map_err(ExecuteLayerDefinitionError::DeleteLayerError)?; create_layer( @@ -67,12 +67,11 @@ where // Always write the layer types as: // a) they might be different from what is currently on disk // b) the cache field will be removed by CNB lifecycle on cache restore - crate::layer::replace_layer_types(layers_dir, &layer_name, layer_types) - .map_err(|error| { - ExecuteLayerDefinitionError::WriteLayerError( - WriteLayerError::WriteLayerMetadataError(error), - ) - })?; + replace_layer_types(layers_dir, &layer_name, layer_types).map_err(|error| { + ExecuteLayerDefinitionError::WriteLayerError( + WriteLayerError::WriteLayerMetadataError(error), + ) + })?; Ok(LayerRef { name: layer_data.name, @@ -95,7 +94,7 @@ where match invalid_metadata_action { (InvalidMetadataAction::DeleteLayer, cause) => { - crate::layer::handling::delete_layer(layers_dir, &layer_name) + delete_layer(layers_dir, &layer_name) .map_err(ExecuteLayerDefinitionError::DeleteLayerError)?; create_layer( @@ -106,12 +105,11 @@ where ) } (InvalidMetadataAction::ReplaceMetadata(metadata), _) => { - crate::layer::replace_layer_metadata(layers_dir, &layer_name, metadata) - .map_err(|error| { - ExecuteLayerDefinitionError::WriteLayerError( - WriteLayerError::WriteLayerMetadataError(error), - ) - })?; + replace_layer_metadata(layers_dir, &layer_name, metadata).map_err(|error| { + ExecuteLayerDefinitionError::WriteLayerError( + WriteLayerError::WriteLayerMetadataError(error), + ) + })?; execute( layer_types, @@ -138,25 +136,21 @@ fn create_layer( where B: Buildpack + ?Sized, { - crate::layer::handling::write_layer( + crate::layer::shared::write_layer( layers_dir, layer_name, - &LayerEnv::new(), &LayerContentMetadata { types: Some(layer_types), metadata: GenericMetadata::default(), }, - ExecDPrograms::Keep, - Sboms::Keep, ) .map_err(ExecuteLayerDefinitionError::WriteLayerError)?; - let layer_data = - crate::layer::handling::read_layer::(layers_dir, layer_name) - .map_err(ExecuteLayerDefinitionError::ReadLayerError)? - .ok_or(ExecuteLayerDefinitionError::CouldNotReadLayerAfterCreate( - layer_name.clone(), - ))?; + let layer_data = read_layer::(layers_dir, layer_name) + .map_err(ExecuteLayerDefinitionError::ReadLayerError)? + .ok_or(ExecuteLayerDefinitionError::CouldNotReadLayerAfterCreate( + layer_name.clone(), + ))?; Ok(LayerRef { name: layer_data.name, From 18bf79ea656dcbf7fc0a8e8cc3d6b93744fd40a6 Mon Sep 17 00:00:00 2001 From: Manuel Fuchs Date: Mon, 8 Apr 2024 16:06:25 +0200 Subject: [PATCH 04/29] Make compilation succeed again --- libcnb/src/build.rs | 18 +- libcnb/src/error.rs | 10 +- libcnb/src/layer/mod.rs | 9 +- libcnb/src/layer/shared.rs | 120 ++++++- libcnb/src/layer/struct_api/execute.rs | 52 +-- libcnb/src/layer/struct_api/mod.rs | 25 +- .../{layer_api => trait_api}/handling.rs | 295 +++++------------- .../src/layer/{layer_api => trait_api}/mod.rs | 6 +- .../layer/{layer_api => trait_api}/tests.rs | 2 +- 9 files changed, 254 insertions(+), 283 deletions(-) rename libcnb/src/layer/{layer_api => trait_api}/handling.rs (77%) rename libcnb/src/layer/{layer_api => trait_api}/mod.rs (98%) rename libcnb/src/layer/{layer_api => trait_api}/tests.rs (99%) diff --git a/libcnb/src/build.rs b/libcnb/src/build.rs index 72846a05..a1528916 100644 --- a/libcnb/src/build.rs +++ b/libcnb/src/build.rs @@ -6,9 +6,10 @@ use crate::data::store::Store; use crate::data::{ buildpack::ComponentBuildpackDescriptor, buildpack_plan::BuildpackPlan, launch::Launch, }; +use crate::layer::handling::LayerErrorOrBuildpackError; use crate::layer::{ - CachedLayerDefinition, HandleLayerErrorOrBuildpackError, InspectExistingAction, IntoAction, - InvalidMetadataAction, Layer, LayerData, LayerRef, UncachedLayerDefinition, + CachedLayerDefinition, InspectExistingAction, IntoAction, InvalidMetadataAction, LayerRef, + UncachedLayerDefinition, }; use crate::sbom::Sbom; use crate::Target; @@ -104,16 +105,17 @@ impl BuildContext { /// } /// ``` #[deprecated = "The Layer trait API was replaced by LayerDefinitions. Use `cached_layer` and `uncached_layer`."] - pub fn handle_layer>( + #[allow(deprecated)] + pub fn handle_layer>( &self, layer_name: LayerName, layer: L, - ) -> crate::Result, B::Error> { - crate::layer::handle_layer(self, layer_name, layer).map_err(|error| match error { - HandleLayerErrorOrBuildpackError::HandleLayerError(e) => { - crate::Error::HandleLayerError(e) + ) -> crate::Result, B::Error> { + crate::layer::trait_api::handling::handle_layer(self, layer_name, layer).map_err(|error| { + match error { + LayerErrorOrBuildpackError::LayerError(e) => crate::Error::LayerError(e), + LayerErrorOrBuildpackError::BuildpackError(e) => crate::Error::BuildpackError(e), } - HandleLayerErrorOrBuildpackError::BuildpackError(e) => crate::Error::BuildpackError(e), }) } diff --git a/libcnb/src/error.rs b/libcnb/src/error.rs index 965d8bb5..dfbf2889 100644 --- a/libcnb/src/error.rs +++ b/libcnb/src/error.rs @@ -1,6 +1,5 @@ use crate::data::launch::ProcessTypeError; -use crate::layer::execute::ExecuteLayerDefinitionError; -use crate::layer::HandleLayerError; +use crate::layer::LayerError; use libcnb_common::toml_file::TomlFileError; use std::fmt::Debug; @@ -12,11 +11,8 @@ pub type Result = std::result::Result>; /// An error that occurred during buildpack execution. #[derive(thiserror::Error, Debug)] pub enum Error { - #[error("HandleLayer error: {0}")] - HandleLayerError(#[from] HandleLayerError), - - #[error("ExecuteLayerDefinitionError error: {0}")] - ExecuteLayerDefinitionError(#[from] ExecuteLayerDefinitionError), + #[error("Layer error: {0}")] + LayerError(#[from] LayerError), #[error("Process type error: {0}")] ProcessTypeError(#[from] ProcessTypeError), diff --git a/libcnb/src/layer/mod.rs b/libcnb/src/layer/mod.rs index 2f365702..06fdbc2f 100644 --- a/libcnb/src/layer/mod.rs +++ b/libcnb/src/layer/mod.rs @@ -1,8 +1,9 @@ //! Provides types and helpers to work with layers. -mod layer_api; -pub(in crate::layer) mod shared; -mod struct_api; +pub(crate) mod shared; +pub(crate) mod struct_api; +pub(crate) mod trait_api; -pub use layer_api::*; +pub use shared::LayerError; pub use struct_api::*; +pub use trait_api::*; diff --git a/libcnb/src/layer/shared.rs b/libcnb/src/layer/shared.rs index c6d5d412..1e3f8c9b 100644 --- a/libcnb/src/layer/shared.rs +++ b/libcnb/src/layer/shared.rs @@ -1,12 +1,15 @@ // This lint triggers when both layer_dir and layers_dir are present which are quite common. #![allow(clippy::similar_names)] +use crate::sbom::{cnb_sbom_path, Sbom}; use crate::util::{default_on_not_found, remove_dir_recursively}; use libcnb_common::toml_file::{read_toml_file, write_toml_file, TomlFileError}; use libcnb_data::layer::LayerName; use libcnb_data::layer_content_metadata::{LayerContentMetadata, LayerTypes}; +use libcnb_data::sbom::SBOM_FORMATS; use serde::de::DeserializeOwned; use serde::Serialize; +use std::collections::HashMap; use std::fs; use std::path::{Path, PathBuf}; @@ -77,16 +80,24 @@ pub(in crate::layer) fn write_layer>( ) -> Result<(), WriteLayerError> { let layers_dir = layers_dir.as_ref(); fs::create_dir_all(layers_dir.join(layer_name.as_str()))?; - replace_layer_metadata(layers_dir, layer_name, layer_content_metadata)?; + replace_layer_metadata(layers_dir, layer_name, layer_content_metadata) + .map_err(WriteLayerError::WriteLayerMetadataError)?; Ok(()) } #[derive(thiserror::Error, Debug)] +#[allow(clippy::enum_variant_names)] pub enum WriteLayerError { #[error("Layer content metadata couldn't be parsed!")] WriteLayerMetadataError(WriteLayerMetadataError), + #[error("TODO")] + ReplaceLayerSbomsError(ReplaceLayerSbomsError), + + #[error("TODO")] + ReplaceLayerExecdProgramsError(ReplaceLayerExecdProgramsError), + #[error("Unexpected I/O error while writing layer: {0}")] IoError(#[from] std::io::Error), } @@ -145,6 +156,95 @@ pub(crate) fn replace_layer_types>( .map_err(WriteLayerMetadataError::TomlFileError) } +pub(crate) fn replace_layer_sboms>( + layers_dir: P, + layer_name: &LayerName, + sboms: &[Sbom], +) -> Result<(), ReplaceLayerSbomsError> { + let layers_dir = layers_dir.as_ref(); + + if !layers_dir.join(layer_name.as_str()).is_dir() { + return Err(ReplaceLayerSbomsError::MissingLayer(layer_name.clone())); + } + + for format in SBOM_FORMATS { + default_on_not_found(fs::remove_file(cnb_sbom_path( + format, layers_dir, layer_name, + )))?; + } + + for sbom in sboms { + fs::write( + cnb_sbom_path(&sbom.format, layers_dir, layer_name), + &sbom.data, + )?; + } + + Ok(()) +} + +#[derive(thiserror::Error, Debug)] +pub enum ReplaceLayerSbomsError { + #[error("Layer doesn't exist: {0}")] + MissingLayer(LayerName), + + #[error("Unexpected I/O error while replacing layer SBOMs: {0}")] + IoError(#[from] std::io::Error), +} + +pub(in crate::layer) fn replace_layer_exec_d_programs>( + layers_dir: P, + layer_name: &LayerName, + exec_d_programs: &HashMap, +) -> Result<(), ReplaceLayerExecdProgramsError> { + let layer_dir = layers_dir.as_ref().join(layer_name.as_str()); + + if !layer_dir.is_dir() { + return Err(ReplaceLayerExecdProgramsError::MissingLayer( + layer_name.clone(), + )); + } + + let exec_d_dir = layer_dir.join("exec.d"); + + if exec_d_dir.is_dir() { + fs::remove_dir_all(&exec_d_dir)?; + } + + if !exec_d_programs.is_empty() { + fs::create_dir_all(&exec_d_dir)?; + + for (name, path) in exec_d_programs { + // We could just try to copy the file here and let the call-site deal with the + // I/O errors when the path does not exist. We're using an explicit error variant + // for a missing exec.d binary makes it easier to debug issues with packaging + // since the usage of exec.d binaries often relies on implicit packaging the + // buildpack author might not be aware of. + Some(&path) + .filter(|path| path.exists()) + .ok_or_else(|| ReplaceLayerExecdProgramsError::MissingExecDFile(path.clone())) + .and_then(|path| { + fs::copy(path, exec_d_dir.join(name)) + .map_err(ReplaceLayerExecdProgramsError::IoError) + })?; + } + } + + Ok(()) +} + +#[derive(thiserror::Error, Debug)] +pub enum ReplaceLayerExecdProgramsError { + #[error("Unexpected I/O error while replacing layer execd programs: {0}")] + IoError(#[from] std::io::Error), + + #[error("Couldn't find exec.d file for copying: {0}")] + MissingExecDFile(PathBuf), + + #[error("Layer doesn't exist: {0}")] + MissingLayer(LayerName), +} + #[derive(thiserror::Error, Debug)] pub enum WriteLayerMetadataError { #[error("Unexpected I/O error while writing layer metadata: {0}")] @@ -153,3 +253,21 @@ pub enum WriteLayerMetadataError { #[error("Error while writing layer content metadata TOML: {0}")] TomlFileError(#[from] TomlFileError), } + +#[derive(thiserror::Error, Debug)] +pub enum LayerError { + #[error("{0}")] + ReadLayerError(#[from] ReadLayerError), + #[error("{0}")] + WriteLayerError(#[from] WriteLayerError), + #[error("{0}")] + DeleteLayerError(#[from] DeleteLayerError), + #[error("Cannot read generic layer metadata: {0}")] + CouldNotReadGenericLayerMetadata(TomlFileError), + #[error("Cannot read layer {0} after creating it")] + CouldNotReadLayerAfterCreate(LayerName), + #[error("Unexpected I/O error: {0}")] + IoError(#[from] std::io::Error), + #[error("Unexpected missing layer")] + UnexpectedMissingLayer, +} diff --git a/libcnb/src/layer/struct_api/execute.rs b/libcnb/src/layer/struct_api/execute.rs index e8e0ba36..17d16fab 100644 --- a/libcnb/src/layer/struct_api/execute.rs +++ b/libcnb/src/layer/struct_api/execute.rs @@ -1,12 +1,13 @@ use crate::layer::shared::{ - delete_layer, read_layer, replace_layer_metadata, replace_layer_types, DeleteLayerError, - ReadLayerError, WriteLayerError, + delete_layer, read_layer, replace_layer_metadata, replace_layer_types, ReadLayerError, + WriteLayerError, }; use crate::layer::{ - EmptyReason, InspectExistingAction, IntoAction, InvalidMetadataAction, LayerContents, LayerRef, + EmptyReason, InspectExistingAction, IntoAction, InvalidMetadataAction, LayerContents, + LayerError, LayerRef, }; use crate::Buildpack; -use libcnb_common::toml_file::{read_toml_file, TomlFileError}; +use libcnb_common::toml_file::read_toml_file; use libcnb_data::generic::GenericMetadata; use libcnb_data::layer::LayerName; use libcnb_data::layer_content_metadata::{LayerContentMetadata, LayerTypes}; @@ -15,21 +16,6 @@ use serde::Serialize; use std::marker::PhantomData; use std::path::{Path, PathBuf}; -#[derive(thiserror::Error, Debug)] -#[allow(clippy::enum_variant_names)] -pub enum ExecuteLayerDefinitionError { - #[error("{0}")] - ReadLayerError(#[from] ReadLayerError), - #[error("{0}")] - WriteLayerError(#[from] WriteLayerError), - #[error("{0}")] - DeleteLayerError(#[from] DeleteLayerError), - #[error("Cannot read generic layer metadata: {0}")] - CouldNotReadGenericLayerMetadata(TomlFileError), - #[error("Cannot read layer {0} after creating it")] - CouldNotReadLayerAfterCreate(LayerName), -} - #[allow(clippy::too_many_lines)] pub(crate) fn execute( layer_types: LayerTypes, @@ -53,8 +39,7 @@ where match inspect_action { (InspectExistingAction::Delete, cause) => { - delete_layer(layers_dir, &layer_name) - .map_err(ExecuteLayerDefinitionError::DeleteLayerError)?; + delete_layer(layers_dir, &layer_name).map_err(LayerError::DeleteLayerError)?; create_layer( layer_types, @@ -68,9 +53,7 @@ where // a) they might be different from what is currently on disk // b) the cache field will be removed by CNB lifecycle on cache restore replace_layer_types(layers_dir, &layer_name, layer_types).map_err(|error| { - ExecuteLayerDefinitionError::WriteLayerError( - WriteLayerError::WriteLayerMetadataError(error), - ) + LayerError::WriteLayerError(WriteLayerError::WriteLayerMetadataError(error)) })?; Ok(LayerRef { @@ -86,7 +69,7 @@ where let layer_content_metadata = read_toml_file::( layers_dir.join(format!("{}.toml", &layer_name)), ) - .map_err(ExecuteLayerDefinitionError::CouldNotReadGenericLayerMetadata)?; + .map_err(LayerError::CouldNotReadGenericLayerMetadata)?; let invalid_metadata_action = invalid_metadata(&layer_content_metadata.metadata) .into_action() @@ -94,8 +77,7 @@ where match invalid_metadata_action { (InvalidMetadataAction::DeleteLayer, cause) => { - delete_layer(layers_dir, &layer_name) - .map_err(ExecuteLayerDefinitionError::DeleteLayerError)?; + delete_layer(layers_dir, &layer_name).map_err(LayerError::DeleteLayerError)?; create_layer( layer_types, @@ -106,9 +88,7 @@ where } (InvalidMetadataAction::ReplaceMetadata(metadata), _) => { replace_layer_metadata(layers_dir, &layer_name, metadata).map_err(|error| { - ExecuteLayerDefinitionError::WriteLayerError( - WriteLayerError::WriteLayerMetadataError(error), - ) + LayerError::WriteLayerError(WriteLayerError::WriteLayerMetadataError(error)) })?; execute( @@ -121,9 +101,7 @@ where } } } - Err(read_layer_error) => Err(ExecuteLayerDefinitionError::ReadLayerError( - read_layer_error, - ))?, + Err(read_layer_error) => Err(LayerError::ReadLayerError(read_layer_error))?, } } @@ -144,13 +122,11 @@ where metadata: GenericMetadata::default(), }, ) - .map_err(ExecuteLayerDefinitionError::WriteLayerError)?; + .map_err(LayerError::WriteLayerError)?; let layer_data = read_layer::(layers_dir, layer_name) - .map_err(ExecuteLayerDefinitionError::ReadLayerError)? - .ok_or(ExecuteLayerDefinitionError::CouldNotReadLayerAfterCreate( - layer_name.clone(), - ))?; + .map_err(LayerError::ReadLayerError)? + .ok_or(LayerError::CouldNotReadLayerAfterCreate(layer_name.clone()))?; Ok(LayerRef { name: layer_data.name, diff --git a/libcnb/src/layer/struct_api/mod.rs b/libcnb/src/layer/struct_api/mod.rs index f1529f54..645a6966 100644 --- a/libcnb/src/layer/struct_api/mod.rs +++ b/libcnb/src/layer/struct_api/mod.rs @@ -1,7 +1,7 @@ pub(crate) mod execute; -use crate::layer::handling::{replace_layer_exec_d_programs, replace_layer_sboms}; -use crate::layer::{HandleLayerError, WriteLayerError}; +use crate::layer::shared::{replace_layer_exec_d_programs, replace_layer_sboms, WriteLayerError}; +use crate::layer::LayerError; use crate::layer_env::LayerEnv; use crate::sbom::Sbom; use crate::Buildpack; @@ -129,26 +129,23 @@ where where M: Serialize, { - crate::layer::replace_layer_metadata(&self.layers_dir, &self.name, metadata).map_err( - |error| { - crate::Error::HandleLayerError(HandleLayerError::WriteLayerError( + crate::layer::shared::replace_layer_metadata(&self.layers_dir, &self.name, metadata) + .map_err(|error| { + crate::Error::LayerError(LayerError::WriteLayerError( WriteLayerError::WriteLayerMetadataError(error), )) - }, - ) + }) } - pub fn replace_env(&self, env: LayerEnv) -> crate::Result<(), B::Error> { - env.write_to_layer_dir(self.path()).map_err(|a| { - crate::Error::HandleLayerError(HandleLayerError::WriteLayerError( - WriteLayerError::WriteLayerEnvError(a), - )) + pub fn replace_env(&self, env: &LayerEnv) -> crate::Result<(), B::Error> { + env.write_to_layer_dir(self.path()).map_err(|error| { + crate::Error::LayerError(LayerError::WriteLayerError(WriteLayerError::IoError(error))) }) } pub fn replace_sboms(&self, sboms: &[Sbom]) -> crate::Result<(), B::Error> { replace_layer_sboms(&self.layers_dir, &self.name, sboms).map_err(|error| { - crate::Error::HandleLayerError(HandleLayerError::WriteLayerError( + crate::Error::LayerError(LayerError::WriteLayerError( WriteLayerError::ReplaceLayerSbomsError(error), )) }) @@ -165,7 +162,7 @@ where .collect::>(); replace_layer_exec_d_programs(&self.layers_dir, &self.name, &programs).map_err(|error| { - crate::Error::HandleLayerError(HandleLayerError::WriteLayerError( + crate::Error::LayerError(LayerError::WriteLayerError( WriteLayerError::ReplaceLayerExecdProgramsError(error), )) }) diff --git a/libcnb/src/layer/layer_api/handling.rs b/libcnb/src/layer/trait_api/handling.rs similarity index 77% rename from libcnb/src/layer/layer_api/handling.rs rename to libcnb/src/layer/trait_api/handling.rs index c95f62c6..bf3df4fa 100644 --- a/libcnb/src/layer/layer_api/handling.rs +++ b/libcnb/src/layer/trait_api/handling.rs @@ -7,17 +7,14 @@ use crate::data::layer::LayerName; use crate::data::layer_content_metadata::LayerContentMetadata; use crate::generic::GenericMetadata; use crate::layer::shared::{ - delete_layer, DeleteLayerError, ReadLayerError, WriteLayerMetadataError, + delete_layer, replace_layer_exec_d_programs, replace_layer_sboms, ReadLayerError, + WriteLayerError, WriteLayerMetadataError, }; -use crate::layer::{ExistingLayerStrategy, LayerData, MetadataMigration}; +use crate::layer::{ExistingLayerStrategy, LayerData, LayerError, MetadataMigration}; use crate::layer_env::LayerEnv; -use crate::sbom::{cnb_sbom_path, Sbom}; -use crate::util::{default_on_not_found, remove_dir_recursively}; +use crate::sbom::Sbom; +use crate::write_toml_file; use crate::Buildpack; -use crate::{write_toml_file, TomlFileError}; -use libcnb_common::toml_file::read_toml_file; -use libcnb_data::layer_content_metadata::LayerTypes; -use libcnb_data::sbom::SBOM_FORMATS; use serde::de::DeserializeOwned; use serde::Serialize; use std::collections::HashMap; @@ -28,17 +25,20 @@ pub(crate) fn handle_layer>( context: &BuildContext, layer_name: LayerName, mut layer: L, -) -> Result, HandleLayerErrorOrBuildpackError> { +) -> Result, LayerErrorOrBuildpackError> { match read_layer(&context.layers_dir, &layer_name) { Ok(None) => handle_create_layer(context, &layer_name, &mut layer), Ok(Some(layer_data)) => { let existing_layer_strategy = layer .existing_layer_strategy(context, &layer_data) - .map_err(HandleLayerErrorOrBuildpackError::BuildpackError)?; + .map_err(LayerErrorOrBuildpackError::BuildpackError)?; match existing_layer_strategy { ExistingLayerStrategy::Recreate => { - delete_layer(&context.layers_dir, &layer_name)?; + delete_layer(&context.layers_dir, &layer_name).map_err(|error| { + LayerErrorOrBuildpackError::LayerError(LayerError::DeleteLayerError(error)) + })?; + handle_create_layer(context, &layer_name, &mut layer) } ExistingLayerStrategy::Update => { @@ -60,13 +60,21 @@ pub(crate) fn handle_layer>( }, ExecDPrograms::Keep, Sboms::Keep, - )?; + ) + .map_err(|error| { + LayerErrorOrBuildpackError::LayerError(LayerError::WriteLayerError(error)) + })?; // Reread the layer from disk to ensure the returned layer data accurately reflects // the state on disk after we messed with it. - read_layer(&context.layers_dir, &layer_name)? - .ok_or(HandleLayerError::UnexpectedMissingLayer) - .map_err(HandleLayerErrorOrBuildpackError::HandleLayerError) + read_layer(&context.layers_dir, &layer_name) + .map_err(|error| { + LayerErrorOrBuildpackError::LayerError(LayerError::ReadLayerError( + error, + )) + })? + .ok_or(LayerError::UnexpectedMissingLayer) + .map_err(LayerErrorOrBuildpackError::LayerError) } } } @@ -78,11 +86,15 @@ pub(crate) fn handle_layer>( context, &generic_layer_data.content_metadata.metadata, ) - .map_err(HandleLayerErrorOrBuildpackError::BuildpackError)?; + .map_err(LayerErrorOrBuildpackError::BuildpackError)?; match metadata_migration_strategy { MetadataMigration::RecreateLayer => { - delete_layer(&context.layers_dir, &layer_name)?; + delete_layer(&context.layers_dir, &layer_name).map_err(|error| { + LayerErrorOrBuildpackError::LayerError( + LayerError::DeleteLayerError(error), + ) + })?; } MetadataMigration::ReplaceMetadata(migrated_metadata) => { write_layer( @@ -95,19 +107,28 @@ pub(crate) fn handle_layer>( }, ExecDPrograms::Keep, Sboms::Keep, - )?; + ) + .map_err(|error| { + LayerErrorOrBuildpackError::LayerError(LayerError::WriteLayerError( + error, + )) + })?; } } handle_layer(context, layer_name, layer) } - Ok(None) => Err(HandleLayerError::UnexpectedMissingLayer.into()), - Err(read_layer_error) => { - Err(HandleLayerError::ReadLayerError(read_layer_error).into()) - } + Ok(None) => Err(LayerErrorOrBuildpackError::LayerError( + LayerError::UnexpectedMissingLayer, + )), + Err(read_layer_error) => Err(LayerErrorOrBuildpackError::LayerError( + LayerError::ReadLayerError(read_layer_error), + )), } } - Err(read_layer_error) => Err(HandleLayerError::ReadLayerError(read_layer_error).into()), + Err(read_layer_error) => Err(LayerErrorOrBuildpackError::LayerError( + LayerError::ReadLayerError(read_layer_error), + )), } } @@ -115,16 +136,16 @@ fn handle_create_layer>( context: &BuildContext, layer_name: &LayerName, layer: &mut L, -) -> Result, HandleLayerErrorOrBuildpackError> { +) -> Result, LayerErrorOrBuildpackError> { let layer_dir = context.layers_dir.join(layer_name.as_str()); fs::create_dir_all(&layer_dir) - .map_err(HandleLayerError::IoError) - .map_err(HandleLayerErrorOrBuildpackError::HandleLayerError)?; + .map_err(LayerError::IoError) + .map_err(LayerErrorOrBuildpackError::LayerError)?; let layer_result = layer .create(context, &layer_dir) - .map_err(HandleLayerErrorOrBuildpackError::BuildpackError)?; + .map_err(LayerErrorOrBuildpackError::BuildpackError)?; write_layer( &context.layers_dir, @@ -136,21 +157,23 @@ fn handle_create_layer>( }, ExecDPrograms::Replace(layer_result.exec_d_programs), Sboms::Replace(layer_result.sboms), - )?; + ) + .map_err(|error| LayerErrorOrBuildpackError::LayerError(LayerError::WriteLayerError(error)))?; - read_layer(&context.layers_dir, layer_name)? - .ok_or(HandleLayerError::UnexpectedMissingLayer) - .map_err(HandleLayerErrorOrBuildpackError::HandleLayerError) + read_layer(&context.layers_dir, layer_name) + .map_err(|error| LayerErrorOrBuildpackError::LayerError(LayerError::ReadLayerError(error)))? + .ok_or(LayerError::UnexpectedMissingLayer) + .map_err(LayerErrorOrBuildpackError::LayerError) } fn handle_update_layer>( context: &BuildContext, layer_data: &LayerData, layer: &mut L, -) -> Result, HandleLayerErrorOrBuildpackError> { +) -> Result, LayerErrorOrBuildpackError> { let layer_result = layer .update(context, layer_data) - .map_err(HandleLayerErrorOrBuildpackError::BuildpackError)?; + .map_err(LayerErrorOrBuildpackError::BuildpackError)?; write_layer( &context.layers_dir, @@ -162,104 +185,21 @@ fn handle_update_layer>( }, ExecDPrograms::Replace(layer_result.exec_d_programs), Sboms::Replace(layer_result.sboms), - )?; + ) + .map_err(|error| LayerErrorOrBuildpackError::LayerError(LayerError::WriteLayerError(error)))?; - read_layer(&context.layers_dir, &layer_data.name)? - .ok_or(HandleLayerError::UnexpectedMissingLayer) - .map_err(HandleLayerErrorOrBuildpackError::HandleLayerError) + read_layer(&context.layers_dir, &layer_data.name) + .map_err(|error| LayerErrorOrBuildpackError::LayerError(LayerError::ReadLayerError(error)))? + .ok_or(LayerError::UnexpectedMissingLayer) + .map_err(LayerErrorOrBuildpackError::LayerError) } #[derive(Debug)] -pub(crate) enum HandleLayerErrorOrBuildpackError { - HandleLayerError(HandleLayerError), +pub(crate) enum LayerErrorOrBuildpackError { + LayerError(LayerError), BuildpackError(E), } -impl From for HandleLayerErrorOrBuildpackError { - fn from(e: HandleLayerError) -> Self { - Self::HandleLayerError(e) - } -} - -impl From for HandleLayerErrorOrBuildpackError { - fn from(e: DeleteLayerError) -> Self { - Self::HandleLayerError(HandleLayerError::DeleteLayerError(e)) - } -} - -impl From for HandleLayerErrorOrBuildpackError { - fn from(e: ReadLayerError) -> Self { - Self::HandleLayerError(HandleLayerError::ReadLayerError(e)) - } -} - -impl From for HandleLayerErrorOrBuildpackError { - fn from(e: WriteLayerError) -> Self { - Self::HandleLayerError(HandleLayerError::WriteLayerError(e)) - } -} - -impl From for HandleLayerErrorOrBuildpackError { - fn from(e: std::io::Error) -> Self { - Self::HandleLayerError(HandleLayerError::IoError(e)) - } -} - -#[derive(thiserror::Error, Debug)] -pub enum HandleLayerError { - #[error("Unexpected I/O error while handling layer: {0}")] - IoError(#[from] std::io::Error), - - #[error("Unexpected DeleteLayerError while handling layer: {0}")] - DeleteLayerError(#[from] DeleteLayerError), - - #[error("Unexpected ReadLayerError while handling layer: {0}")] - ReadLayerError(#[from] ReadLayerError), - - #[error("Unexpected WriteLayerError while handling layer: {0}")] - WriteLayerError(#[from] WriteLayerError), - - #[error("Expected layer to be present, but it was missing")] - UnexpectedMissingLayer, -} - -#[derive(thiserror::Error, Debug)] -#[allow(clippy::enum_variant_names)] -pub enum WriteLayerError { - #[error("{0}")] - WriteLayerEnvError(#[from] std::io::Error), - - #[error("{0}")] - WriteLayerMetadataError(#[from] WriteLayerMetadataError), - - #[error("{0}")] - ReplaceLayerExecdProgramsError(#[from] ReplaceLayerExecdProgramsError), - - #[error("{0}")] - ReplaceLayerSbomsError(#[from] ReplaceLayerSbomsError), -} - -#[derive(thiserror::Error, Debug)] -pub enum ReplaceLayerExecdProgramsError { - #[error("Unexpected I/O error while replacing layer execd programs: {0}")] - IoError(#[from] std::io::Error), - - #[error("Couldn't find exec.d file for copying: {0}")] - MissingExecDFile(PathBuf), - - #[error("Layer doesn't exist: {0}")] - MissingLayer(LayerName), -} - -#[derive(thiserror::Error, Debug)] -pub enum ReplaceLayerSbomsError { - #[error("Layer doesn't exist: {0}")] - MissingLayer(LayerName), - - #[error("Unexpected I/O error while replacing layer SBOMs: {0}")] - IoError(#[from] std::io::Error), -} - #[derive(Debug)] pub(in crate::layer) enum ExecDPrograms { Keep, @@ -272,74 +212,6 @@ pub(in crate::layer) enum Sboms { Replace(Vec), } -pub(in crate::layer) fn replace_layer_sboms>( - layers_dir: P, - layer_name: &LayerName, - sboms: &[Sbom], -) -> Result<(), ReplaceLayerSbomsError> { - let layers_dir = layers_dir.as_ref(); - - if !layers_dir.join(layer_name.as_str()).is_dir() { - return Err(ReplaceLayerSbomsError::MissingLayer(layer_name.clone())); - } - - for format in SBOM_FORMATS { - default_on_not_found(fs::remove_file(cnb_sbom_path( - format, layers_dir, layer_name, - )))?; - } - - for sbom in sboms { - fs::write( - cnb_sbom_path(&sbom.format, layers_dir, layer_name), - &sbom.data, - )?; - } - - Ok(()) -} - -pub(in crate::layer) fn replace_layer_exec_d_programs>( - layers_dir: P, - layer_name: &LayerName, - exec_d_programs: &HashMap, -) -> Result<(), ReplaceLayerExecdProgramsError> { - let layer_dir = layers_dir.as_ref().join(layer_name.as_str()); - - if !layer_dir.is_dir() { - return Err(ReplaceLayerExecdProgramsError::MissingLayer( - layer_name.clone(), - )); - } - - let exec_d_dir = layer_dir.join("exec.d"); - - if exec_d_dir.is_dir() { - fs::remove_dir_all(&exec_d_dir)?; - } - - if !exec_d_programs.is_empty() { - fs::create_dir_all(&exec_d_dir)?; - - for (name, path) in exec_d_programs { - // We could just try to copy the file here and let the call-site deal with the - // I/O errors when the path does not exist. We're using an explicit error variant - // for a missing exec.d binary makes it easier to debug issues with packaging - // since the usage of exec.d binaries often relies on implicit packaging the - // buildpack author might not be aware of. - Some(&path) - .filter(|path| path.exists()) - .ok_or_else(|| ReplaceLayerExecdProgramsError::MissingExecDFile(path.clone())) - .and_then(|path| { - fs::copy(path, exec_d_dir.join(name)) - .map_err(ReplaceLayerExecdProgramsError::IoError) - })?; - } - } - - Ok(()) -} - pub(in crate::layer) fn write_layer_metadata>( layers_dir: P, layer_name: &LayerName, @@ -365,17 +237,20 @@ pub(in crate::layer) fn write_layer>( ) -> Result<(), WriteLayerError> { let layers_dir = layers_dir.as_ref(); - write_layer_metadata(layers_dir, layer_name, layer_content_metadata)?; + write_layer_metadata(layers_dir, layer_name, layer_content_metadata) + .map_err(WriteLayerError::WriteLayerMetadataError)?; let layer_dir = layers_dir.join(layer_name.as_str()); layer_env.write_to_layer_dir(layer_dir)?; if let Sboms::Replace(sboms) = layer_sboms { - replace_layer_sboms(layers_dir, layer_name, &sboms)?; + replace_layer_sboms(layers_dir, layer_name, &sboms) + .map_err(WriteLayerError::ReplaceLayerSbomsError)?; } if let ExecDPrograms::Replace(exec_d_programs) = layer_exec_d_programs { - replace_layer_exec_d_programs(layers_dir, layer_name, &exec_d_programs)?; + replace_layer_exec_d_programs(layers_dir, layer_name, &exec_d_programs) + .map_err(WriteLayerError::ReplaceLayerExecdProgramsError)?; } Ok(()) @@ -385,32 +260,34 @@ pub(crate) fn read_layer>( layers_dir: P, layer_name: &LayerName, ) -> Result>, ReadLayerError> { - crate::layer::shared::read_layer(layers_dir, layer_name).map(|read_layer| { - read_layer.map(|read_layer| { - - LayerEnv::read_from_layer_dir(&read_layer.path).map_err(ReadLayerError::IoError) - - - LayerEnv::read_from_layer_dir(&read_layer.path).map(|layer_env| LayerData { - name: read_layer.name, - path: read_layer.path, - content_metadata: read_layer.metadata, - env: layer_env, + crate::layer::shared::read_layer(layers_dir, layer_name).and_then(|layer| { + layer + .map(|layer| { + LayerEnv::read_from_layer_dir(&layer.path) + .map_err(ReadLayerError::IoError) + .map(|env| LayerData { + name: layer.name, + path: layer.path, + env, + content_metadata: layer.metadata, + }) }) - }) + .transpose() }) } #[cfg(test)] mod tests { use super::*; - use crate::data::layer_content_metadata::LayerTypes; + use crate::data::layer_content_metadata::{LayerContentMetadata, LayerTypes}; use crate::data::layer_name; + use crate::generic::GenericMetadata; + use crate::layer::shared::ReplaceLayerExecdProgramsError; use crate::layer_env::{ModificationBehavior, Scope}; use crate::read_toml_file; use serde::Deserialize; use std::ffi::OsString; - + use std::fs; use tempfile::tempdir; #[test] diff --git a/libcnb/src/layer/layer_api/mod.rs b/libcnb/src/layer/trait_api/mod.rs similarity index 98% rename from libcnb/src/layer/layer_api/mod.rs rename to libcnb/src/layer/trait_api/mod.rs index 4117656f..74977718 100644 --- a/libcnb/src/layer/layer_api/mod.rs +++ b/libcnb/src/layer/trait_api/mod.rs @@ -1,3 +1,7 @@ +// The whole API is deprecated and relies on itself for implementation. To avoid necessary warnings, +// the use of deprecated code is allowed in this module. +#![allow(deprecated)] + use crate::build::BuildContext; use crate::data::layer::LayerName; use crate::data::layer_content_metadata::{LayerContentMetadata, LayerTypes}; @@ -10,7 +14,7 @@ use serde::Serialize; use std::collections::HashMap; use std::path::{Path, PathBuf}; -mod handling; +pub(crate) mod handling; #[cfg(test)] mod tests; diff --git a/libcnb/src/layer/layer_api/tests.rs b/libcnb/src/layer/trait_api/tests.rs similarity index 99% rename from libcnb/src/layer/layer_api/tests.rs rename to libcnb/src/layer/trait_api/tests.rs index a54cd8e1..83b0ab52 100644 --- a/libcnb/src/layer/layer_api/tests.rs +++ b/libcnb/src/layer/trait_api/tests.rs @@ -14,7 +14,7 @@ use crate::data::buildpack_id; use crate::data::layer_content_metadata::LayerTypes; use crate::detect::{DetectContext, DetectResult, DetectResultBuilder}; use crate::generic::{GenericMetadata, GenericPlatform}; -use crate::layer::layer_api::handling::handle_layer; +use crate::layer::trait_api::handling::handle_layer; use crate::layer::{ ExistingLayerStrategy, Layer, LayerData, LayerResult, LayerResultBuilder, MetadataMigration, }; From 10c2caf933b99b5968b3aae27e0cc5ddc45983d0 Mon Sep 17 00:00:00 2001 From: Manuel Fuchs Date: Mon, 8 Apr 2024 16:33:08 +0200 Subject: [PATCH 05/29] Fix RustDoc --- libcnb/src/build.rs | 4 ++-- libcnb/src/layer/trait_api/mod.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/libcnb/src/build.rs b/libcnb/src/build.rs index a1528916..3ad66afd 100644 --- a/libcnb/src/build.rs +++ b/libcnb/src/build.rs @@ -33,13 +33,13 @@ pub struct BuildContext { } impl BuildContext { - /// Handles the given [`Layer`] implementation in this context. + /// Handles the given [`crate::layer::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 + /// Use the returned [`crate::layer::LayerData`] to access the layers metadata and environment variables for /// subsequent logic or layers. /// /// # Example: diff --git a/libcnb/src/layer/trait_api/mod.rs b/libcnb/src/layer/trait_api/mod.rs index 74977718..dca2c777 100644 --- a/libcnb/src/layer/trait_api/mod.rs +++ b/libcnb/src/layer/trait_api/mod.rs @@ -269,8 +269,8 @@ impl LayerResultBuilder { /// /// This method returns the [`LayerResult`] wrapped in a [`Result`] even though its technically /// not fallible. This is done to simplify using this method in the contexts it's most often - /// used in: a layer's [create](crate::layer::LayerRef::create) and/or - /// [update](crate::layer::LayerRef::update) methods. + /// used in: a layer's [create](Layer::create) and/or + /// [update](Layer::update) methods. /// /// See [`build_unwrapped`](Self::build_unwrapped) for an unwrapped version of this method. pub fn build(self) -> Result, E> { From 7bc68d0f501df6cb5243c8cae5b8f4a5aca13b0b Mon Sep 17 00:00:00 2001 From: Manuel Fuchs Date: Tue, 9 Apr 2024 10:14:56 +0200 Subject: [PATCH 06/29] Add `#![allow(deprecated)]` to example and test buildpacks --- examples/execd/src/main.rs | 4 ++++ test-buildpacks/readonly-layer-files/src/main.rs | 4 ++++ test-buildpacks/sbom/src/main.rs | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/examples/execd/src/main.rs b/examples/execd/src/main.rs index b395f751..84c2fc42 100644 --- a/examples/execd/src/main.rs +++ b/examples/execd/src/main.rs @@ -1,3 +1,7 @@ +// This example uses the older trait Layer API. The example will be updated to the newer API +// before the next libcnb.rs release. +#![allow(deprecated)] + mod layer; use crate::layer::ExecDLayer; diff --git a/test-buildpacks/readonly-layer-files/src/main.rs b/test-buildpacks/readonly-layer-files/src/main.rs index 3d7340f8..96b264fa 100644 --- a/test-buildpacks/readonly-layer-files/src/main.rs +++ b/test-buildpacks/readonly-layer-files/src/main.rs @@ -1,3 +1,7 @@ +// This test buildpack uses the older trait Layer API. It will be updated to the newer API +// before the next libcnb.rs release. +#![allow(deprecated)] + mod layer; use crate::layer::TestLayer; diff --git a/test-buildpacks/sbom/src/main.rs b/test-buildpacks/sbom/src/main.rs index ce021365..f8543cf7 100644 --- a/test-buildpacks/sbom/src/main.rs +++ b/test-buildpacks/sbom/src/main.rs @@ -1,3 +1,7 @@ +// This test buildpack uses the older trait Layer API. It will be updated to the newer API +// before the next libcnb.rs release. +#![allow(deprecated)] + mod test_layer; mod test_layer_2; From 194035d9485fecefe73a7a99e25e8b1f97caff39 Mon Sep 17 00:00:00 2001 From: Manuel Fuchs Date: Tue, 9 Apr 2024 10:48:31 +0200 Subject: [PATCH 07/29] Remove no longer necessary clippy allow --- libcnb/src/layer/struct_api/execute.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/libcnb/src/layer/struct_api/execute.rs b/libcnb/src/layer/struct_api/execute.rs index 17d16fab..e1a054f4 100644 --- a/libcnb/src/layer/struct_api/execute.rs +++ b/libcnb/src/layer/struct_api/execute.rs @@ -16,7 +16,6 @@ use serde::Serialize; use std::marker::PhantomData; use std::path::{Path, PathBuf}; -#[allow(clippy::too_many_lines)] pub(crate) fn execute( layer_types: LayerTypes, invalid_metadata: &dyn Fn(&GenericMetadata) -> MA, From 3fad3c4c2a15735a407780344f4e97c984371e3f Mon Sep 17 00:00:00 2001 From: Manuel Fuchs Date: Tue, 9 Apr 2024 10:50:36 +0200 Subject: [PATCH 08/29] Fix replace_layer_types visibility --- libcnb/src/layer/shared.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libcnb/src/layer/shared.rs b/libcnb/src/layer/shared.rs index 1e3f8c9b..5860a2b8 100644 --- a/libcnb/src/layer/shared.rs +++ b/libcnb/src/layer/shared.rs @@ -141,7 +141,7 @@ pub(in crate::layer) fn replace_layer_metadata>( .map_err(WriteLayerMetadataError::TomlFileError) } -pub(crate) fn replace_layer_types>( +pub(in crate::layer) fn replace_layer_types>( layers_dir: P, layer_name: &LayerName, layer_types: LayerTypes, From 239c01c69974b489726514413d4d1f010ce019df Mon Sep 17 00:00:00 2001 From: Manuel Fuchs Date: Tue, 9 Apr 2024 10:53:44 +0200 Subject: [PATCH 09/29] Properly expose layer errors --- libcnb/src/layer/mod.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libcnb/src/layer/mod.rs b/libcnb/src/layer/mod.rs index 06fdbc2f..70139dc3 100644 --- a/libcnb/src/layer/mod.rs +++ b/libcnb/src/layer/mod.rs @@ -4,6 +4,10 @@ pub(crate) mod shared; pub(crate) mod struct_api; pub(crate) mod trait_api; +pub use shared::DeleteLayerError; pub use shared::LayerError; +pub use shared::ReadLayerError; +pub use shared::WriteLayerError; + pub use struct_api::*; pub use trait_api::*; From 96d010d8e20f2c62db0b358eb28a0233d21b303c Mon Sep 17 00:00:00 2001 From: Manuel Fuchs Date: Tue, 9 Apr 2024 11:14:29 +0200 Subject: [PATCH 10/29] Move shared tests for read_layer to shared module --- libcnb/src/layer/shared.rs | 180 ++++++++++++++++++++++++ libcnb/src/layer/trait_api/handling.rs | 187 ------------------------- 2 files changed, 180 insertions(+), 187 deletions(-) diff --git a/libcnb/src/layer/shared.rs b/libcnb/src/layer/shared.rs index 5860a2b8..8dfb1269 100644 --- a/libcnb/src/layer/shared.rs +++ b/libcnb/src/layer/shared.rs @@ -271,3 +271,183 @@ pub enum LayerError { #[error("Unexpected missing layer")] UnexpectedMissingLayer, } + +#[cfg(test)] +mod test { + use crate::layer::ReadLayerError; + use libcnb_data::generic::GenericMetadata; + use libcnb_data::layer_content_metadata::{LayerContentMetadata, LayerTypes}; + use libcnb_data::layer_name; + use serde::Deserialize; + use std::fs; + use tempfile::tempdir; + + #[test] + fn read_layer() { + #[derive(Deserialize, Debug, Eq, PartialEq)] + struct TestLayerMetadata { + version: String, + sha: String, + } + + let layer_name = layer_name!("foo"); + let temp_dir = tempdir().unwrap(); + let layers_dir = temp_dir.path(); + let layer_dir = layers_dir.join(layer_name.as_str()); + + fs::create_dir_all(&layer_dir).unwrap(); + fs::write( + layers_dir.join(format!("{layer_name}.toml")), + r#" + [types] + launch = true + build = false + cache = true + + [metadata] + version = "1.0" + sha = "2608a36467a6fec50be1672bfbf88b04b9ec8efaafa58c71d9edf73519ed8e2c" + "#, + ) + .unwrap(); + + let layer_data = super::read_layer::(layers_dir, &layer_name) + .unwrap() + .unwrap(); + + assert_eq!(layer_data.path, layer_dir); + + assert_eq!(layer_data.name, layer_name); + + assert_eq!( + layer_data.metadata.types, + Some(LayerTypes { + launch: true, + build: false, + cache: true + }) + ); + + assert_eq!( + layer_data.metadata.metadata, + TestLayerMetadata { + version: String::from("1.0"), + sha: String::from( + "2608a36467a6fec50be1672bfbf88b04b9ec8efaafa58c71d9edf73519ed8e2c" + ) + } + ); + } + + #[test] + fn read_malformed_toml_layer() { + let layer_name = layer_name!("foo"); + let temp_dir = tempdir().unwrap(); + let layers_dir = temp_dir.path(); + let layer_dir = layers_dir.join(layer_name.as_str()); + + fs::create_dir_all(layer_dir).unwrap(); + fs::write( + layers_dir.join(format!("{layer_name}.toml")), + r" + [types + build = true + launch = true + cache = true + ", + ) + .unwrap(); + + match super::read_layer::(layers_dir, &layer_name) { + Err(ReadLayerError::LayerContentMetadataParseError(toml_error)) => { + assert_eq!(toml_error.span(), Some(19..20)); + } + _ => panic!("Expected ReadLayerError::LayerContentMetadataParseError!"), + } + } + + #[test] + fn read_incompatible_metadata_layer() { + #[derive(Deserialize, Debug, Eq, PartialEq)] + struct TestLayerMetadata { + version: String, + sha: String, + } + + let layer_name = layer_name!("foo"); + let temp_dir = tempdir().unwrap(); + let layers_dir = temp_dir.path(); + let layer_dir = layers_dir.join(layer_name.as_str()); + + fs::create_dir_all(layer_dir).unwrap(); + fs::write( + layers_dir.join(format!("{layer_name}.toml")), + r#" + [types] + build = true + launch = true + cache = true + + [metadata] + version = "1.0" + "#, + ) + .unwrap(); + + match super::read_layer::(layers_dir, &layer_name) { + Err(ReadLayerError::LayerContentMetadataParseError(toml_error)) => { + assert_eq!(toml_error.span(), Some(110..148)); + } + _ => panic!("Expected ReadLayerError::LayerContentMetadataParseError!"), + } + } + + #[test] + fn read_layer_without_layer_directory() { + let layer_name = layer_name!("foo"); + let temp_dir = tempdir().unwrap(); + let layers_dir = temp_dir.path(); + let layer_dir = layers_dir.join(layer_name.as_str()); + + fs::create_dir_all(layer_dir).unwrap(); + + match super::read_layer::(layers_dir, &layer_name) { + Ok(Some(layer_data)) => { + assert_eq!( + layer_data.metadata, + LayerContentMetadata { + types: None, + metadata: None + } + ); + } + _ => panic!("Expected Ok(Some(_)!"), + } + } + + #[test] + fn read_layer_without_layer_content_metadata() { + let layer_name = layer_name!("foo"); + let temp_dir = tempdir().unwrap(); + let layers_dir = temp_dir.path(); + + fs::write(layers_dir.join(format!("{layer_name}.toml")), "").unwrap(); + + match super::read_layer::(layers_dir, &layer_name) { + Ok(None) => {} + _ => panic!("Expected Ok(None)!"), + } + } + + #[test] + fn read_nonexistent_layer() { + let layer_name = layer_name!("foo"); + let temp_dir = tempdir().unwrap(); + let layers_dir = temp_dir.path(); + + match super::read_layer::(layers_dir, &layer_name) { + Ok(None) => {} + _ => panic!("Expected Ok(None)!"), + } + } +} diff --git a/libcnb/src/layer/trait_api/handling.rs b/libcnb/src/layer/trait_api/handling.rs index bf3df4fa..07547c0d 100644 --- a/libcnb/src/layer/trait_api/handling.rs +++ b/libcnb/src/layer/trait_api/handling.rs @@ -687,191 +687,4 @@ mod tests { assert!(!layer_dir.join("exec.d").exists()); } - - #[test] - fn read_layer() { - #[derive(Deserialize, Debug, Eq, PartialEq)] - struct TestLayerMetadata { - version: String, - sha: String, - } - - let layer_name = layer_name!("foo"); - let temp_dir = tempdir().unwrap(); - let layers_dir = temp_dir.path(); - let layer_dir = layers_dir.join(layer_name.as_str()); - - fs::create_dir_all(&layer_dir).unwrap(); - fs::write( - layers_dir.join(format!("{layer_name}.toml")), - r#" - [types] - launch = true - build = false - cache = true - - [metadata] - version = "1.0" - sha = "2608a36467a6fec50be1672bfbf88b04b9ec8efaafa58c71d9edf73519ed8e2c" - "#, - ) - .unwrap(); - - // Add a bin directory to test if implicit entries are added to the LayerEnv - fs::create_dir_all(layer_dir.join("bin")).unwrap(); - - // Add a file to the env directory to test if explicit entries are added to the LayerEnv - fs::create_dir_all(layer_dir.join("env")).unwrap(); - fs::write(layer_dir.join("env/CUSTOM_ENV"), "CUSTOM_ENV_VALUE").unwrap(); - - let layer_data = super::read_layer::(layers_dir, &layer_name) - .unwrap() - .unwrap(); - - assert_eq!(layer_data.path, layer_dir); - - assert_eq!(layer_data.name, layer_name); - - assert_eq!( - layer_data.content_metadata.types, - Some(LayerTypes { - launch: true, - build: false, - cache: true - }) - ); - - assert_eq!( - layer_data.content_metadata.metadata, - TestLayerMetadata { - version: String::from("1.0"), - sha: String::from( - "2608a36467a6fec50be1672bfbf88b04b9ec8efaafa58c71d9edf73519ed8e2c" - ) - } - ); - - let applied_layer_env = layer_data.env.apply_to_empty(Scope::Build); - assert_eq!( - applied_layer_env.get("PATH").cloned(), - Some(layer_dir.join("bin").into()) - ); - - assert_eq!( - applied_layer_env.get("CUSTOM_ENV"), - Some(&OsString::from("CUSTOM_ENV_VALUE")) - ); - } - - #[test] - fn read_malformed_toml_layer() { - let layer_name = layer_name!("foo"); - let temp_dir = tempdir().unwrap(); - let layers_dir = temp_dir.path(); - let layer_dir = layers_dir.join(layer_name.as_str()); - - fs::create_dir_all(layer_dir).unwrap(); - fs::write( - layers_dir.join(format!("{layer_name}.toml")), - r" - [types - build = true - launch = true - cache = true - ", - ) - .unwrap(); - - match super::read_layer::(layers_dir, &layer_name) { - Err(ReadLayerError::LayerContentMetadataParseError(toml_error)) => { - assert_eq!(toml_error.span(), Some(19..20)); - } - _ => panic!("Expected ReadLayerError::LayerContentMetadataParseError!"), - } - } - - #[test] - fn read_incompatible_metadata_layer() { - #[derive(Deserialize, Debug, Eq, PartialEq)] - struct TestLayerMetadata { - version: String, - sha: String, - } - - let layer_name = layer_name!("foo"); - let temp_dir = tempdir().unwrap(); - let layers_dir = temp_dir.path(); - let layer_dir = layers_dir.join(layer_name.as_str()); - - fs::create_dir_all(layer_dir).unwrap(); - fs::write( - layers_dir.join(format!("{layer_name}.toml")), - r#" - [types] - build = true - launch = true - cache = true - - [metadata] - version = "1.0" - "#, - ) - .unwrap(); - - match super::read_layer::(layers_dir, &layer_name) { - Err(ReadLayerError::LayerContentMetadataParseError(toml_error)) => { - assert_eq!(toml_error.span(), Some(110..148)); - } - _ => panic!("Expected ReadLayerError::LayerContentMetadataParseError!"), - } - } - - #[test] - fn read_layer_without_layer_directory() { - let layer_name = layer_name!("foo"); - let temp_dir = tempdir().unwrap(); - let layers_dir = temp_dir.path(); - let layer_dir = layers_dir.join(layer_name.as_str()); - - fs::create_dir_all(layer_dir).unwrap(); - - match super::read_layer::(layers_dir, &layer_name) { - Ok(Some(layer_data)) => { - assert_eq!( - layer_data.content_metadata, - LayerContentMetadata { - types: None, - metadata: None - } - ); - } - _ => panic!("Expected Ok(Some(_)!"), - } - } - - #[test] - fn read_layer_without_layer_content_metadata() { - let layer_name = layer_name!("foo"); - let temp_dir = tempdir().unwrap(); - let layers_dir = temp_dir.path(); - - fs::write(layers_dir.join(format!("{layer_name}.toml")), "").unwrap(); - - match super::read_layer::(layers_dir, &layer_name) { - Ok(None) => {} - _ => panic!("Expected Ok(None)!"), - } - } - - #[test] - fn read_nonexistent_layer() { - let layer_name = layer_name!("foo"); - let temp_dir = tempdir().unwrap(); - let layers_dir = temp_dir.path(); - - match super::read_layer::(layers_dir, &layer_name) { - Ok(None) => {} - _ => panic!("Expected Ok(None)!"), - } - } } From 9d2ee4249b924e538c8b45ea67aad2e2c5aec1ea Mon Sep 17 00:00:00 2001 From: Manuel Fuchs Date: Tue, 9 Apr 2024 13:24:44 +0200 Subject: [PATCH 11/29] Move write_layer to shared code --- libcnb/src/layer/shared.rs | 11 +++++++---- libcnb/src/layer/trait_api/handling.rs | 22 ++-------------------- 2 files changed, 9 insertions(+), 24 deletions(-) diff --git a/libcnb/src/layer/shared.rs b/libcnb/src/layer/shared.rs index 8dfb1269..dc4e11a6 100644 --- a/libcnb/src/layer/shared.rs +++ b/libcnb/src/layer/shared.rs @@ -72,15 +72,18 @@ pub enum ReadLayerError { IoError(#[from] std::io::Error), } -/// Updates layer metadata on disk pub(in crate::layer) fn write_layer>( layers_dir: P, layer_name: &LayerName, layer_content_metadata: &LayerContentMetadata, ) -> Result<(), WriteLayerError> { - let layers_dir = layers_dir.as_ref(); - fs::create_dir_all(layers_dir.join(layer_name.as_str()))?; - replace_layer_metadata(layers_dir, layer_name, layer_content_metadata) + let layer_dir = layers_dir.as_ref().join(layer_name.as_str()); + fs::create_dir_all(layer_dir)?; + + let layer_content_metadata_path = layers_dir.as_ref().join(format!("{layer_name}.toml")); + + write_toml_file(&layer_content_metadata, layer_content_metadata_path) + .map_err(WriteLayerMetadataError::TomlFileError) .map_err(WriteLayerError::WriteLayerMetadataError)?; Ok(()) diff --git a/libcnb/src/layer/trait_api/handling.rs b/libcnb/src/layer/trait_api/handling.rs index 07547c0d..1ad3de61 100644 --- a/libcnb/src/layer/trait_api/handling.rs +++ b/libcnb/src/layer/trait_api/handling.rs @@ -8,12 +8,11 @@ use crate::data::layer_content_metadata::LayerContentMetadata; use crate::generic::GenericMetadata; use crate::layer::shared::{ delete_layer, replace_layer_exec_d_programs, replace_layer_sboms, ReadLayerError, - WriteLayerError, WriteLayerMetadataError, + WriteLayerError, }; use crate::layer::{ExistingLayerStrategy, LayerData, LayerError, MetadataMigration}; use crate::layer_env::LayerEnv; use crate::sbom::Sbom; -use crate::write_toml_file; use crate::Buildpack; use serde::de::DeserializeOwned; use serde::Serialize; @@ -212,20 +211,6 @@ pub(in crate::layer) enum Sboms { Replace(Vec), } -pub(in crate::layer) fn write_layer_metadata>( - layers_dir: P, - layer_name: &LayerName, - layer_content_metadata: &LayerContentMetadata, -) -> Result<(), WriteLayerMetadataError> { - let layer_dir = layers_dir.as_ref().join(layer_name.as_str()); - fs::create_dir_all(layer_dir)?; - - let layer_content_metadata_path = layers_dir.as_ref().join(format!("{layer_name}.toml")); - write_toml_file(&layer_content_metadata, layer_content_metadata_path)?; - - Ok(()) -} - /// Updates layer metadata on disk pub(in crate::layer) fn write_layer>( layers_dir: P, @@ -237,8 +222,7 @@ pub(in crate::layer) fn write_layer>( ) -> Result<(), WriteLayerError> { let layers_dir = layers_dir.as_ref(); - write_layer_metadata(layers_dir, layer_name, layer_content_metadata) - .map_err(WriteLayerError::WriteLayerMetadataError)?; + crate::layer::shared::write_layer(layers_dir, layer_name, layer_content_metadata)?; let layer_dir = layers_dir.join(layer_name.as_str()); layer_env.write_to_layer_dir(layer_dir)?; @@ -285,8 +269,6 @@ mod tests { use crate::layer::shared::ReplaceLayerExecdProgramsError; use crate::layer_env::{ModificationBehavior, Scope}; use crate::read_toml_file; - use serde::Deserialize; - use std::ffi::OsString; use std::fs; use tempfile::tempdir; From 9984be32f3d0f00a2403df7248e9385b91e38465 Mon Sep 17 00:00:00 2001 From: Manuel Fuchs Date: Wed, 10 Apr 2024 14:41:00 +0200 Subject: [PATCH 12/29] Fix WriteLayerError Display impl --- libcnb/src/layer/shared.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libcnb/src/layer/shared.rs b/libcnb/src/layer/shared.rs index dc4e11a6..46158f9c 100644 --- a/libcnb/src/layer/shared.rs +++ b/libcnb/src/layer/shared.rs @@ -92,13 +92,13 @@ pub(in crate::layer) fn write_layer>( #[derive(thiserror::Error, Debug)] #[allow(clippy::enum_variant_names)] pub enum WriteLayerError { - #[error("Layer content metadata couldn't be parsed!")] + #[error("{0}")] WriteLayerMetadataError(WriteLayerMetadataError), - #[error("TODO")] + #[error("{0}")] ReplaceLayerSbomsError(ReplaceLayerSbomsError), - #[error("TODO")] + #[error("{0}")] ReplaceLayerExecdProgramsError(ReplaceLayerExecdProgramsError), #[error("Unexpected I/O error while writing layer: {0}")] From 408000d5b71d5ff5b0a5f99d68b5760c854fe3c0 Mon Sep 17 00:00:00 2001 From: Manuel Fuchs Date: Wed, 10 Apr 2024 14:46:50 +0200 Subject: [PATCH 13/29] Rename execute to handle --- libcnb/src/build.rs | 6 +++--- libcnb/src/layer/struct_api/{execute.rs => handling.rs} | 4 ++-- libcnb/src/layer/struct_api/mod.rs | 3 +-- 3 files changed, 6 insertions(+), 7 deletions(-) rename libcnb/src/layer/struct_api/{execute.rs => handling.rs} (98%) diff --git a/libcnb/src/build.rs b/libcnb/src/build.rs index 3ad66afd..aade6553 100644 --- a/libcnb/src/build.rs +++ b/libcnb/src/build.rs @@ -6,7 +6,7 @@ use crate::data::store::Store; use crate::data::{ buildpack::ComponentBuildpackDescriptor, buildpack_plan::BuildpackPlan, launch::Launch, }; -use crate::layer::handling::LayerErrorOrBuildpackError; +use crate::layer::trait_api::handling::LayerErrorOrBuildpackError; use crate::layer::{ CachedLayerDefinition, InspectExistingAction, IntoAction, InvalidMetadataAction, LayerRef, UncachedLayerDefinition, @@ -126,7 +126,7 @@ impl BuildContext { ) -> crate::Result, B::Error> { let layer_definition = layer_definition.borrow(); - crate::layer::execute( + crate::layer::struct_api::handling::handle_layer( LayerTypes { launch: layer_definition.launch, build: layer_definition.build, @@ -151,7 +151,7 @@ impl BuildContext { { let layer_definition = layer_definition.borrow(); - crate::layer::execute( + crate::layer::struct_api::handling::handle_layer( LayerTypes { launch: layer_definition.launch, build: layer_definition.build, diff --git a/libcnb/src/layer/struct_api/execute.rs b/libcnb/src/layer/struct_api/handling.rs similarity index 98% rename from libcnb/src/layer/struct_api/execute.rs rename to libcnb/src/layer/struct_api/handling.rs index e1a054f4..f8e00c53 100644 --- a/libcnb/src/layer/struct_api/execute.rs +++ b/libcnb/src/layer/struct_api/handling.rs @@ -16,7 +16,7 @@ use serde::Serialize; use std::marker::PhantomData; use std::path::{Path, PathBuf}; -pub(crate) fn execute( +pub(crate) fn handle_layer( layer_types: LayerTypes, invalid_metadata: &dyn Fn(&GenericMetadata) -> MA, inspect_existing: &dyn Fn(&M, &Path) -> IA, @@ -90,7 +90,7 @@ where LayerError::WriteLayerError(WriteLayerError::WriteLayerMetadataError(error)) })?; - execute( + handle_layer( layer_types, invalid_metadata, inspect_existing, diff --git a/libcnb/src/layer/struct_api/mod.rs b/libcnb/src/layer/struct_api/mod.rs index 645a6966..faf42354 100644 --- a/libcnb/src/layer/struct_api/mod.rs +++ b/libcnb/src/layer/struct_api/mod.rs @@ -1,11 +1,10 @@ -pub(crate) mod execute; +pub(crate) mod handling; use crate::layer::shared::{replace_layer_exec_d_programs, replace_layer_sboms, WriteLayerError}; use crate::layer::LayerError; use crate::layer_env::LayerEnv; use crate::sbom::Sbom; use crate::Buildpack; -pub(crate) use execute::execute; use libcnb_data::generic::GenericMetadata; use libcnb_data::layer::LayerName; use serde::Serialize; From b0d5aed19e5e74d48c886443a3b40e31790cae6f Mon Sep 17 00:00:00 2001 From: Manuel Fuchs Date: Thu, 13 Jun 2024 12:25:02 +0200 Subject: [PATCH 14/29] Apply miscellaneous polish --- libcnb/src/build.rs | 268 ++++++++++++++++++++++-- libcnb/src/layer/struct_api/handling.rs | 39 ++-- libcnb/src/layer/struct_api/mod.rs | 104 ++++++--- libcnb/src/layer/trait_api/mod.rs | 12 +- 4 files changed, 357 insertions(+), 66 deletions(-) diff --git a/libcnb/src/build.rs b/libcnb/src/build.rs index aade6553..e220405e 100644 --- a/libcnb/src/build.rs +++ b/libcnb/src/build.rs @@ -104,7 +104,7 @@ impl BuildContext { /// } /// } /// ``` - #[deprecated = "The Layer trait API was replaced by LayerDefinitions. Use `cached_layer` and `uncached_layer`."] + #[deprecated = "The Layer trait API was replaced by a struct based API. Use `cached_layer` and `uncached_layer`."] #[allow(deprecated)] pub fn handle_layer>( &self, @@ -119,46 +119,278 @@ impl BuildContext { }) } - pub fn uncached_layer( + /// Creates a cached layer, potentially re-using a previously cached version. + /// + /// Buildpack code uses this function to create a cached layer and will get back a reference to + /// the layer directory on disk. Intricacies of the CNB spec are automatically handled such as + /// the maintenance of TOML files. Buildpack code can also specify a callback for cached layer + /// invalidation. + /// + /// Users of this function pass in a [`CachedLayerDefinition`] that describes the desired layer + /// and the returned `LayerRef` can then be used to modify the layer like any other path. This + /// allows users to be flexible when and how the layer is modified and to abstract layer + /// creation away if necessary. + /// + /// # Basic Example + /// ```rust + /// # use libcnb::build::{BuildContext, BuildResult, BuildResultBuilder}; + /// # use libcnb::detect::{DetectContext, DetectResult}; + /// # use libcnb::generic::GenericPlatform; + /// # use libcnb::layer::{ + /// # CachedLayerDefinition, InspectExistingAction, InvalidMetadataAction, LayerContents, + /// # }; + /// # use libcnb::layer_env::{LayerEnv, ModificationBehavior, Scope}; + /// # use libcnb::Buildpack; + /// # use libcnb_data::generic::GenericMetadata; + /// # use libcnb_data::layer_name; + /// # use std::fs; + /// # + /// # struct ExampleBuildpack; + /// # + /// # #[derive(Debug)] + /// # enum ExampleBuildpackError { + /// # WriteDataError(std::io::Error), + /// # } + /// # + /// # impl Buildpack for ExampleBuildpack { + /// # type Platform = GenericPlatform; + /// # type Metadata = GenericMetadata; + /// # type Error = ExampleBuildpackError; + /// # + /// # fn detect(&self, context: DetectContext) -> libcnb::Result { + /// # unimplemented!() + /// # } + /// # + /// # fn build(&self, context: BuildContext) -> libcnb::Result { + /// let layer_ref = context.cached_layer( + /// layer_name!("example_layer"), + /// CachedLayerDefinition { + /// build: false, + /// launch: false, + /// // Will be called if a cached version of the layer was found, but the metadata + /// // could not be parsed. In this example, we instruct libcnb to always delete the + /// // existing layer in such a case. But we can implement any logic here if we want. + /// invalid_metadata: &|_| InvalidMetadataAction::DeleteLayer, + /// // Will be called if a cached version of the layer was found. This allows us to + /// // inspect the contents and metadata to decide if we want to keep the existing + /// // layer or let libcnb delete the existing layer and create a new one for us. + /// // This is libcnb's method to implement cache invalidations for layers. + /// inspect_existing: &|_: &GenericMetadata, _| InspectExistingAction::KeepLayer, + /// }, + /// )?; + /// + /// // At this point, a layer exists on disk. It might contain cached data or might be empty. + /// // Since we need to conditionally work with the layer contents based on its state, we can + /// // inspect the `contents` field of the layer reference to get detailed information about + /// // the current layer contents and the cause(s) for the state. + /// // + /// // In the majority of cases, we don't need more details beyond if it's empty or not and can + /// // ignore the details. This is what we do in this example. See the later example for a more + /// // complex situation. + /// match layer_ref.contents { + /// LayerContents::Empty { .. } => { + /// println!("Creating new example layer!"); + /// + /// // Modify the layer contents with regular Rust functions: + /// fs::write( + /// layer_ref.path().join("data.txt"), + /// "Here is some example data", + /// ) + /// .map_err(ExampleBuildpackError::WriteDataError)?; + /// + /// // Use functions on LayerRef for common CNB specific layer modifications: + /// layer_ref.replace_env(LayerEnv::new().chainable_insert( + /// Scope::All, + /// ModificationBehavior::Append, + /// "PLANET", + /// "LV-246", + /// ))?; + /// } + /// LayerContents::Cached { .. } => { + /// println!("Reusing example layer from previous run!"); + /// } + /// } + /// # + /// # BuildResultBuilder::new().build() + /// # } + /// # } + /// # + /// # impl From for libcnb::Error { + /// # fn from(value: ExampleBuildpackError) -> Self { + /// # Self::BuildpackError(value) + /// # } + /// # } + /// ``` + /// + /// # More complex example + /// ```rust + /// # use libcnb::build::{BuildContext, BuildResult, BuildResultBuilder}; + /// # use libcnb::detect::{DetectContext, DetectResult}; + /// # use libcnb::generic::GenericPlatform; + /// # use libcnb::layer::{ + /// # CachedLayerDefinition, EmptyLayerCause, InspectExistingAction, InvalidMetadataAction, + /// # LayerContents, + /// # }; + /// # use libcnb::Buildpack; + /// # use libcnb_data::generic::GenericMetadata; + /// # use libcnb_data::layer_name; + /// # use serde::{Deserialize, Serialize}; + /// # use std::fs; + /// # + /// # struct ExampleBuildpack; + /// # + /// # #[derive(Debug)] + /// # enum ExampleBuildpackError { + /// # UnexpectedIoError(std::io::Error), + /// # } + /// # + /// #[derive(Deserialize, Serialize)] + /// struct ExampleLayerMetadata { + /// lang_runtime_version: String, + /// } + /// + /// enum CustomCause { + /// Ok, + /// LegacyVersion, + /// HasBrokenModule, + /// MissingModulesFile, + /// } + /// + /// # impl Buildpack for ExampleBuildpack { + /// # type Platform = GenericPlatform; + /// # type Metadata = GenericMetadata; + /// # type Error = ExampleBuildpackError; + /// # + /// # fn detect(&self, _: DetectContext) -> libcnb::Result { + /// # unimplemented!() + /// # } + /// # + /// fn build(&self, context: BuildContext) -> libcnb::Result { + /// let layer_ref = context.cached_layer( + /// layer_name!("example_layer"), + /// CachedLayerDefinition { + /// build: false, + /// launch: false, + /// invalid_metadata: &|_| InvalidMetadataAction::DeleteLayer, + /// inspect_existing: &|metadata: &ExampleLayerMetadata, layer_dir| { + /// if metadata.lang_runtime_version.starts_with("0.") { + /// Ok(( + /// InspectExistingAction::DeleteLayer, + /// CustomCause::LegacyVersion, + /// )) + /// } else { + /// let file_path = layer_dir.join("modules.txt"); + /// + /// if file_path.is_file() { + /// // This is a fallible operation where an unexpected IO error occurs + /// // during operation. In this example, we chose not to map it to + /// // a layer action but let it automatically "bubble up". This error will + /// // end up in the regular libcnb buildpack on_error. + /// let file_contents = fs::read_to_string(&file_path) + /// .map_err(ExampleBuildpackError::UnexpectedIoError)?; + /// + /// if file_contents == "known-broken-0.1c" { + /// Ok(( + /// InspectExistingAction::DeleteLayer, + /// CustomCause::HasBrokenModule, + /// )) + /// } else { + /// Ok((InspectExistingAction::KeepLayer, CustomCause::Ok)) + /// } + /// } else { + /// Ok(( + /// InspectExistingAction::DeleteLayer, + /// CustomCause::MissingModulesFile, + /// )) + /// } + /// } + /// }, + /// }, + /// )?; + /// + /// match layer_ref.contents { + /// LayerContents::Empty { ref cause } => { + /// // Since the cause is just a regular Rust value, we can match it with regular + /// // Rust syntax and be as complex or simple as we need. + /// let message = match cause { + /// EmptyLayerCause::Inspect { + /// cause: CustomCause::LegacyVersion, + /// } => "Re-installing language runtime (legacy cached version)", + /// EmptyLayerCause::Inspect { + /// cause: CustomCause::HasBrokenModule | CustomCause::MissingModulesFile, + /// } => "Re-installing language runtime (broken modules detected)", + /// _ => "Installing language runtime", + /// }; + /// + /// println!("{message}"); + /// + /// // Code to install the language runtime would go here + /// + /// layer_ref.replace_metadata(ExampleLayerMetadata { + /// lang_runtime_version: String::from("1.0.0"), + /// })?; + /// } + /// LayerContents::Cached { .. } => { + /// println!("Re-using cached language runtime"); + /// } + /// } + /// + /// BuildResultBuilder::new().build() + /// } + /// # } + /// # + /// # impl From for libcnb::Error { + /// # fn from(value: ExampleBuildpackError) -> Self { + /// # Self::BuildpackError(value) + /// # } + /// # } + /// ``` + pub fn cached_layer<'a, M, MA, EA, MAC, EAC>( &self, layer_name: LayerName, - layer_definition: impl Borrow, - ) -> crate::Result, B::Error> { + layer_definition: impl Borrow>, + ) -> crate::Result, B::Error> + where + M: 'a + Serialize + DeserializeOwned, + MA: 'a + IntoAction, MAC, B::Error>, + EA: 'a + IntoAction, + { let layer_definition = layer_definition.borrow(); crate::layer::struct_api::handling::handle_layer( LayerTypes { launch: layer_definition.launch, build: layer_definition.build, - cache: false, + cache: true, }, - &|_| InvalidMetadataAction::DeleteLayer, - &|_: &GenericMetadata, _| InspectExistingAction::Delete, + layer_definition.invalid_metadata, + layer_definition.inspect_existing, layer_name, &self.layers_dir, ) } - pub fn cached_layer<'a, M, X, Y, O, I>( + /// Creates an uncached layer. + /// + /// If the layer already exists because it was cached in a previous buildpack run, the existing + /// data will be deleted. + /// + /// This function is essentially the same as [`BuildContext::uncached_layer`] but simpler. + pub fn uncached_layer( &self, layer_name: LayerName, - layer_definition: impl Borrow>, - ) -> crate::Result, B::Error> - where - M: 'a + Serialize + DeserializeOwned, - O: 'a + IntoAction, X, B::Error>, - I: 'a + IntoAction, - { + layer_definition: impl Borrow, + ) -> crate::Result, B::Error> { let layer_definition = layer_definition.borrow(); crate::layer::struct_api::handling::handle_layer( LayerTypes { launch: layer_definition.launch, build: layer_definition.build, - cache: true, + cache: false, }, - layer_definition.invalid_metadata, - layer_definition.inspect_existing, + &|_| InvalidMetadataAction::DeleteLayer, + &|_: &GenericMetadata, _| InspectExistingAction::DeleteLayer, layer_name, &self.layers_dir, ) diff --git a/libcnb/src/layer/struct_api/handling.rs b/libcnb/src/layer/struct_api/handling.rs index f8e00c53..0f037e15 100644 --- a/libcnb/src/layer/struct_api/handling.rs +++ b/libcnb/src/layer/struct_api/handling.rs @@ -3,7 +3,7 @@ use crate::layer::shared::{ WriteLayerError, }; use crate::layer::{ - EmptyReason, InspectExistingAction, IntoAction, InvalidMetadataAction, LayerContents, + EmptyLayerCause, InspectExistingAction, IntoAction, InvalidMetadataAction, LayerContents, LayerError, LayerRef, }; use crate::Buildpack; @@ -16,38 +16,43 @@ use serde::Serialize; use std::marker::PhantomData; use std::path::{Path, PathBuf}; -pub(crate) fn handle_layer( +pub(crate) fn handle_layer( layer_types: LayerTypes, invalid_metadata: &dyn Fn(&GenericMetadata) -> MA, - inspect_existing: &dyn Fn(&M, &Path) -> IA, + inspect_existing: &dyn Fn(&M, &Path) -> EA, layer_name: LayerName, layers_dir: &Path, -) -> crate::Result, B::Error> +) -> crate::Result, B::Error> where B: Buildpack + ?Sized, M: Serialize + DeserializeOwned, - MA: IntoAction, MC, B::Error>, - IA: IntoAction, + MA: IntoAction, MAC, B::Error>, + EA: IntoAction, { match read_layer::(layers_dir, &layer_name) { - Ok(None) => create_layer(layer_types, &layer_name, layers_dir, EmptyReason::Uncached), + Ok(None) => create_layer( + layer_types, + &layer_name, + layers_dir, + EmptyLayerCause::Uncached, + ), Ok(Some(layer_data)) => { let inspect_action = inspect_existing(&layer_data.metadata.metadata, &layer_data.path) .into_action() .map_err(crate::Error::BuildpackError)?; match inspect_action { - (InspectExistingAction::Delete, cause) => { + (InspectExistingAction::DeleteLayer, cause) => { delete_layer(layers_dir, &layer_name).map_err(LayerError::DeleteLayerError)?; create_layer( layer_types, &layer_name, layers_dir, - EmptyReason::Inspect(cause), + EmptyLayerCause::Inspect { cause }, ) } - (InspectExistingAction::Keep, cause) => { + (InspectExistingAction::KeepLayer, cause) => { // Always write the layer types as: // a) they might be different from what is currently on disk // b) the cache field will be removed by CNB lifecycle on cache restore @@ -59,7 +64,7 @@ where name: layer_data.name, layers_dir: PathBuf::from(layers_dir), buildpack: PhantomData, - contents: LayerContents::Cached(cause), + contents: LayerContents::Cached { cause }, }) } } @@ -82,7 +87,7 @@ where layer_types, &layer_name, layers_dir, - EmptyReason::MetadataInvalid(cause), + EmptyLayerCause::MetadataInvalid { cause }, ) } (InvalidMetadataAction::ReplaceMetadata(metadata), _) => { @@ -104,12 +109,12 @@ where } } -fn create_layer( +fn create_layer( layer_types: LayerTypes, layer_name: &LayerName, layers_dir: &Path, - empty_reason: EmptyReason, -) -> Result, crate::Error> + empty_layer_cause: EmptyLayerCause, +) -> Result, crate::Error> where B: Buildpack + ?Sized, { @@ -131,6 +136,8 @@ where name: layer_data.name, layers_dir: PathBuf::from(layers_dir), buildpack: PhantomData, - contents: LayerContents::Empty(empty_reason), + contents: LayerContents::Empty { + cause: empty_layer_cause, + }, }) } diff --git a/libcnb/src/layer/struct_api/mod.rs b/libcnb/src/layer/struct_api/mod.rs index faf42354..27009abe 100644 --- a/libcnb/src/layer/struct_api/mod.rs +++ b/libcnb/src/layer/struct_api/mod.rs @@ -1,5 +1,8 @@ pub(crate) mod handling; +// BuildContext is only used in RustDoc (https://github.com/rust-lang/rust/issues/79542) +#[allow(unused)] +use crate::build::BuildContext; use crate::layer::shared::{replace_layer_exec_d_programs, replace_layer_sboms, WriteLayerError}; use crate::layer::LayerError; use crate::layer_env::LayerEnv; @@ -8,11 +11,14 @@ use crate::Buildpack; use libcnb_data::generic::GenericMetadata; use libcnb_data::layer::LayerName; use serde::Serialize; +use std::borrow::Borrow; use std::collections::HashMap; use std::marker::PhantomData; use std::path::{Path, PathBuf}; /// A definition for a cached layer. +/// +/// Refer to the docs of [`BuildContext::cached_layer`] for usage examples. pub struct CachedLayerDefinition<'a, M, MA, EA> { /// Whether the layer is intended for build. pub build: bool, @@ -29,6 +35,8 @@ pub struct CachedLayerDefinition<'a, M, MA, EA> { } /// A definition for an uncached layer. +/// +/// Refer to the docs of [`BuildContext::uncached_layer`] for usage examples. pub struct UncachedLayerDefinition { /// Whether the layer is intended for build. pub build: bool, @@ -37,93 +45,130 @@ pub struct UncachedLayerDefinition { } /// The action to take when the layer metadata is invalid. -#[derive(Copy, Clone)] +#[derive(Copy, Clone, Debug)] pub enum InvalidMetadataAction { + /// Delete the existing layer. DeleteLayer, + /// Keep the layer, but replace the metadata. Commonly used to migrate to a newer + /// metadata format. ReplaceMetadata(M), } /// The action to take after inspecting existing layer data. -#[derive(Copy, Clone)] +#[derive(Copy, Clone, Debug)] pub enum InspectExistingAction { - Delete, - Keep, + /// Delete the existing layer. + DeleteLayer, + /// Keep the layer as-is. + KeepLayer, } -pub enum LayerContents { +/// Framework metadata about the layer contents. +/// +/// See: [`BuildContext::cached_layer`] and [`BuildContext::uncached_layer`] +#[derive(Copy, Clone, Debug)] +pub enum LayerContents { /// The layer contains validated cached contents from a previous buildpack run. /// /// See: `inspect_existing` in [`CachedLayerDefinition`]. - Cached(Y), - /// The layer is empty. Inspect the contained [`EmptyReason`] for details why. - Empty(EmptyReason), + Cached { cause: EAC }, + /// The layer is empty. Inspect the contained [`EmptyLayerCause`] for the cause. + Empty { cause: EmptyLayerCause }, } -pub enum EmptyReason { - /// The layer wasn't cached in a previous buildpack run. +/// The cause of a layer being empty. +#[derive(Copy, Clone, Debug)] +pub enum EmptyLayerCause { + /// The layer wasn't cached in a previous buildpack run and was freshly created. Uncached, /// The layer was cached in a previous buildpack run, but the metadata was invalid and couldn't /// be converted into a valid form. Subsequently, the layer was deleted entirely. /// /// See: `invalid_metadata` in [`CachedLayerDefinition`]. - MetadataInvalid(X), + MetadataInvalid { cause: MAC }, /// The layer was cached in a previous buildpack run, but the `inspect_existing` function - /// rejected the contents. + /// rejected the contents and/or metadata. /// /// See: `inspect_existing` in [`CachedLayerDefinition`]. - Inspect(Y), + Inspect { cause: EAC }, } /// A value-to-value conversion for layer actions. /// /// Similar to [`Into`], but specialized. Allowing it to also be implemented for /// values in the standard library such as [`Result`]. +/// +/// Implement this trait if you want to use your own types as actions. +/// +/// libcnb ships with generic implementations for the majority of the use-cases: +/// - Using [`InspectExistingAction`] or [`InvalidMetadataAction`] directly. +/// - Using [`InspectExistingAction`] or [`InvalidMetadataAction`] directly, wrapped in a Result. +/// - Using [`InspectExistingAction`] or [`InvalidMetadataAction`] with a cause value in a tuple. +/// - Using [`InspectExistingAction`] or [`InvalidMetadataAction`] with a cause value in a tuple, wrapped in a Result. pub trait IntoAction { fn into_action(self) -> Result<(T, C), E>; } +// Allows to use the layer actions directly. impl IntoAction for T { fn into_action(self) -> Result<(T, ()), E> { Ok((self, ())) } } +// Allows to use the layer actions directly wrapped in a Result. +impl IntoAction for Result { + fn into_action(self) -> Result<(T, ()), E> { + self.map(|value| (value, ())) + } +} + +// Allows to use the layer actions directly with a cause as a tuple. impl IntoAction for (T, C) { fn into_action(self) -> Result<(T, C), E> { Ok(self) } } +// Allows to use the layer actions directly with a cause as a tuple wrapped in a Result. impl IntoAction for Result<(T, C), E> { fn into_action(self) -> Result<(T, C), E> { self } } -impl IntoAction for Result { - fn into_action(self) -> Result<(T, ()), E> { - self.map(|value| (value, ())) - } -} - -pub struct LayerRef +/// A reference to an existing layer on disk. +/// +/// Provides functions to modify the layer such as replacing its metadata, environment, SBOMs or +/// exec.d programs. +/// +/// To obtain a such a reference, use [`BuildContext::cached_layer`] or [`BuildContext::uncached_layer`]. +pub struct LayerRef where B: Buildpack + ?Sized, { name: LayerName, + // Technically not part of the layer itself. However, the functions that modify the layer + // will need a reference to the layers directory as they will also modify files outside the + // actual layer directory. To make LayerRef nice to use, we bite the bullet and include + // the layers_dir here. layers_dir: PathBuf, buildpack: PhantomData, - pub contents: LayerContents, + pub contents: LayerContents, } -impl LayerRef +impl LayerRef where B: Buildpack, { + /// Returns the path to the layer on disk. pub fn path(&self) -> PathBuf { self.layers_dir.join(self.name.as_str()) } + /// Replaces the existing layer metadata with a new value. + /// + /// The new value does not have to be of the same type as the existing metadata. pub fn replace_metadata(&self, metadata: M) -> crate::Result<(), B::Error> where M: Serialize, @@ -136,12 +181,18 @@ where }) } - pub fn replace_env(&self, env: &LayerEnv) -> crate::Result<(), B::Error> { - env.write_to_layer_dir(self.path()).map_err(|error| { - crate::Error::LayerError(LayerError::WriteLayerError(WriteLayerError::IoError(error))) - }) + /// Replaces the existing layer environment with a new one. + pub fn replace_env(&self, env: impl Borrow) -> crate::Result<(), B::Error> { + env.borrow() + .write_to_layer_dir(self.path()) + .map_err(|error| { + crate::Error::LayerError(LayerError::WriteLayerError(WriteLayerError::IoError( + error, + ))) + }) } + /// Replace all existing layer SBOMs with new ones. pub fn replace_sboms(&self, sboms: &[Sbom]) -> crate::Result<(), B::Error> { replace_layer_sboms(&self.layers_dir, &self.name, sboms).map_err(|error| { crate::Error::LayerError(LayerError::WriteLayerError( @@ -150,6 +201,7 @@ where }) } + /// Replace all existing layer exec.d programs with new ones. pub fn replace_exec_d_programs(&self, programs: P) -> crate::Result<(), B::Error> where S: Into, diff --git a/libcnb/src/layer/trait_api/mod.rs b/libcnb/src/layer/trait_api/mod.rs index dca2c777..e6c305c2 100644 --- a/libcnb/src/layer/trait_api/mod.rs +++ b/libcnb/src/layer/trait_api/mod.rs @@ -24,7 +24,7 @@ mod tests; /// depending on its state. To use a `Layer` implementation during build, use /// [`BuildContext::handle_layer`](crate::build::BuildContext::handle_layer). #[allow(unused_variables)] -#[deprecated = "The Layer trait API was replaced by LayerDefinitions and will be removed soon."] +#[deprecated = "The Layer trait API was replaced by a struct based API. Use CachedLayerDefinition or UncachedLayerDefinition."] pub trait Layer { /// The buildpack this layer is used with. type Buildpack: Buildpack; @@ -144,7 +144,7 @@ pub trait Layer { /// The result of a [`Layer::existing_layer_strategy`] call. #[derive(Eq, PartialEq, Clone, Copy, Debug)] -#[deprecated = "The Layer trait API was replaced by LayerDefinitions and will be removed soon."] +#[deprecated = "Part of the Layer trait API that was replaced by a struct based API."] pub enum ExistingLayerStrategy { /// The existing layer should not be modified. Keep, @@ -155,7 +155,7 @@ pub enum ExistingLayerStrategy { } /// The result of a [`Layer::migrate_incompatible_metadata`] call. -#[deprecated = "The Layer trait API was replaced by LayerDefinitions and will be removed soon."] +#[deprecated = "Part of the Layer trait API that was replaced by a struct based API."] pub enum MetadataMigration { /// The layer should be recreated entirely. RecreateLayer, @@ -164,7 +164,7 @@ pub enum MetadataMigration { } /// Information about an existing CNB layer. -#[deprecated = "The Layer trait API was replaced by LayerDefinitions and will be removed soon."] +#[deprecated = "Part of the Layer trait API that was replaced by a struct based API."] pub struct LayerData { pub name: LayerName, /// The layer's path, should not be modified outside of a [`Layer`] implementation. @@ -177,7 +177,7 @@ pub struct LayerData { /// /// Essentially, this carries additional metadata about a layer this later persisted according /// to the CNB spec by libcnb. -#[deprecated = "The Layer trait API was replaced by LayerDefinitions and will be removed soon."] +#[deprecated = "Part of the Layer trait API that was replaced by a struct based API."] pub struct LayerResult { pub metadata: M, pub env: Option, @@ -186,7 +186,7 @@ pub struct LayerResult { } /// A builder that simplifies the creation of [`LayerResult`] values. -#[deprecated = "The Layer trait API was replaced by LayerDefinitions and will be removed soon."] +#[deprecated = "Part of the Layer trait API that was replaced by a struct based API."] pub struct LayerResultBuilder { metadata: M, env: Option, From 8171c641cbcc8c777e17cba0e6d63dd2cd6ce403 Mon Sep 17 00:00:00 2001 From: Manuel Fuchs Date: Fri, 14 Jun 2024 10:53:47 +0200 Subject: [PATCH 15/29] Add struct_api handling tests --- libcnb/src/build.rs | 8 +- libcnb/src/layer/struct_api/handling.rs | 405 +++++++++++++++++++++++- libcnb/src/layer/struct_api/mod.rs | 4 +- 3 files changed, 401 insertions(+), 16 deletions(-) diff --git a/libcnb/src/build.rs b/libcnb/src/build.rs index e220405e..896f3714 100644 --- a/libcnb/src/build.rs +++ b/libcnb/src/build.rs @@ -347,7 +347,7 @@ impl BuildContext { /// ``` pub fn cached_layer<'a, M, MA, EA, MAC, EAC>( &self, - layer_name: LayerName, + layer_name: impl Borrow, layer_definition: impl Borrow>, ) -> crate::Result, B::Error> where @@ -365,7 +365,7 @@ impl BuildContext { }, layer_definition.invalid_metadata, layer_definition.inspect_existing, - layer_name, + layer_name.borrow(), &self.layers_dir, ) } @@ -378,7 +378,7 @@ impl BuildContext { /// This function is essentially the same as [`BuildContext::uncached_layer`] but simpler. pub fn uncached_layer( &self, - layer_name: LayerName, + layer_name: impl Borrow, layer_definition: impl Borrow, ) -> crate::Result, B::Error> { let layer_definition = layer_definition.borrow(); @@ -391,7 +391,7 @@ impl BuildContext { }, &|_| InvalidMetadataAction::DeleteLayer, &|_: &GenericMetadata, _| InspectExistingAction::DeleteLayer, - layer_name, + layer_name.borrow(), &self.layers_dir, ) } diff --git a/libcnb/src/layer/struct_api/handling.rs b/libcnb/src/layer/struct_api/handling.rs index 0f037e15..aaf127cd 100644 --- a/libcnb/src/layer/struct_api/handling.rs +++ b/libcnb/src/layer/struct_api/handling.rs @@ -20,7 +20,7 @@ pub(crate) fn handle_layer( layer_types: LayerTypes, invalid_metadata: &dyn Fn(&GenericMetadata) -> MA, inspect_existing: &dyn Fn(&M, &Path) -> EA, - layer_name: LayerName, + layer_name: &LayerName, layers_dir: &Path, ) -> crate::Result, B::Error> where @@ -29,10 +29,10 @@ where MA: IntoAction, MAC, B::Error>, EA: IntoAction, { - match read_layer::(layers_dir, &layer_name) { + match read_layer::(layers_dir, layer_name) { Ok(None) => create_layer( layer_types, - &layer_name, + layer_name, layers_dir, EmptyLayerCause::Uncached, ), @@ -43,11 +43,11 @@ where match inspect_action { (InspectExistingAction::DeleteLayer, cause) => { - delete_layer(layers_dir, &layer_name).map_err(LayerError::DeleteLayerError)?; + delete_layer(layers_dir, layer_name).map_err(LayerError::DeleteLayerError)?; create_layer( layer_types, - &layer_name, + layer_name, layers_dir, EmptyLayerCause::Inspect { cause }, ) @@ -56,7 +56,7 @@ where // Always write the layer types as: // a) they might be different from what is currently on disk // b) the cache field will be removed by CNB lifecycle on cache restore - replace_layer_types(layers_dir, &layer_name, layer_types).map_err(|error| { + replace_layer_types(layers_dir, layer_name, layer_types).map_err(|error| { LayerError::WriteLayerError(WriteLayerError::WriteLayerMetadataError(error)) })?; @@ -71,7 +71,7 @@ where } Err(ReadLayerError::LayerContentMetadataParseError(_)) => { let layer_content_metadata = read_toml_file::( - layers_dir.join(format!("{}.toml", &layer_name)), + layers_dir.join(format!("{layer_name}.toml")), ) .map_err(LayerError::CouldNotReadGenericLayerMetadata)?; @@ -81,17 +81,17 @@ where match invalid_metadata_action { (InvalidMetadataAction::DeleteLayer, cause) => { - delete_layer(layers_dir, &layer_name).map_err(LayerError::DeleteLayerError)?; + delete_layer(layers_dir, layer_name).map_err(LayerError::DeleteLayerError)?; create_layer( layer_types, - &layer_name, + layer_name, layers_dir, EmptyLayerCause::MetadataInvalid { cause }, ) } (InvalidMetadataAction::ReplaceMetadata(metadata), _) => { - replace_layer_metadata(layers_dir, &layer_name, metadata).map_err(|error| { + replace_layer_metadata(layers_dir, layer_name, metadata).map_err(|error| { LayerError::WriteLayerError(WriteLayerError::WriteLayerMetadataError(error)) })?; @@ -141,3 +141,388 @@ where }, }) } + +#[cfg(test)] +mod tests { + use super::handle_layer; + use crate::build::{BuildContext, BuildResult}; + use crate::detect::{DetectContext, DetectResult}; + use crate::generic::{GenericError, GenericPlatform}; + use crate::layer::{ + EmptyLayerCause, InspectExistingAction, InvalidMetadataAction, LayerContents, + }; + use crate::Buildpack; + use libcnb_common::toml_file::read_toml_file; + use libcnb_data::generic::GenericMetadata; + use libcnb_data::layer_content_metadata::{LayerContentMetadata, LayerTypes}; + use libcnb_data::layer_name; + use serde::{Deserialize, Serialize}; + use tempfile::tempdir; + use toml::toml; + + #[test] + fn create_layer() { + let temp_dir = tempdir().unwrap(); + + let cause = EmptyLayerCause::Inspect { cause: () }; + let layer_name = layer_name!("test_layer"); + let layer_ref = super::create_layer::( + LayerTypes { + launch: true, + build: true, + cache: false, + }, + &layer_name, + temp_dir.path(), + cause, + ) + .unwrap(); + + assert_eq!(layer_ref.layers_dir, temp_dir.path()); + assert_eq!(layer_ref.contents, LayerContents::Empty { cause }); + assert!(temp_dir.path().join(&*layer_name).is_dir()); + assert_eq!( + read_toml_file::>( + temp_dir.path().join(format!("{layer_name}.toml")) + ) + .unwrap(), + LayerContentMetadata { + types: Some(LayerTypes { + launch: true, + build: true, + cache: false, + }), + metadata: GenericMetadata::default() + } + ); + } + + #[test] + fn handle_layer_uncached() { + let temp_dir = tempdir().unwrap(); + + let layer_name = layer_name!("test_layer"); + let layer_ref = handle_layer::< + TestBuildpack, + GenericMetadata, + InvalidMetadataAction, + InspectExistingAction, + (), + (), + >( + LayerTypes { + build: true, + launch: true, + cache: true, + }, + &|_| panic!("invalid_metadata callback should not be called!"), + &|_, _| panic!("inspect_existing callback should not be called!"), + &layer_name, + temp_dir.path(), + ) + .unwrap(); + + assert_eq!(layer_ref.path(), temp_dir.path().join(&*layer_name)); + assert!(layer_ref.path().is_dir()); + assert_eq!( + read_toml_file::>( + temp_dir.path().join(format!("{layer_name}.toml")) + ) + .unwrap(), + LayerContentMetadata { + types: Some(LayerTypes { + build: true, + launch: true, + cache: true, + }), + metadata: GenericMetadata::default() + } + ); + assert_eq!( + layer_ref.contents, + LayerContents::Empty { + cause: EmptyLayerCause::Uncached + } + ); + } + + #[test] + fn handle_layer_cached_keep() { + const KEEP_CAUSE: &str = "cause"; + + let temp_dir = tempdir().unwrap(); + let layer_name = layer_name!("test_layer"); + + // Create a layer as if it was restored by the CNB lifecyle, most notably WITHOUT layer + // types but WITH metadata. + std::fs::create_dir_all(temp_dir.path().join(&*layer_name)).unwrap(); + std::fs::write( + temp_dir.path().join(format!("{layer_name}.toml")), + "[metadata]\nanswer=42", + ) + .unwrap(); + + let layer_ref = + handle_layer::, _, (), _>( + LayerTypes { + build: true, + launch: true, + cache: true, + }, + &|_| panic!("invalid_metadata callback should not be called!"), + &|metadata, path| { + assert_eq!(metadata, &Some(toml! { answer = 42 })); + assert_eq!(path, temp_dir.path().join(&*layer_name.clone())); + (InspectExistingAction::KeepLayer, KEEP_CAUSE) + }, + &layer_name, + temp_dir.path(), + ) + .unwrap(); + + assert_eq!(layer_ref.path(), temp_dir.path().join(&*layer_name)); + assert!(layer_ref.path().is_dir()); + assert_eq!( + read_toml_file::>( + temp_dir.path().join(format!("{layer_name}.toml")) + ) + .unwrap(), + LayerContentMetadata { + types: Some(LayerTypes { + build: true, + launch: true, + cache: true, + }), + metadata: Some(toml! { answer = 42 }) + } + ); + assert_eq!( + layer_ref.contents, + LayerContents::Cached { cause: KEEP_CAUSE } + ); + } + + #[test] + fn handle_layer_cached_delete() { + const DELETE_CAUSE: &str = "cause"; + + let temp_dir = tempdir().unwrap(); + let layer_name = layer_name!("test_layer"); + + // Create a layer as if it was restored by the CNB lifecyle, most notably WITHOUT layer + // types but WITH metadata. + std::fs::create_dir_all(temp_dir.path().join(&*layer_name)).unwrap(); + std::fs::write( + temp_dir.path().join(format!("{layer_name}.toml")), + "[metadata]\nanswer=42", + ) + .unwrap(); + + let layer_ref = + handle_layer::, _, (), _>( + LayerTypes { + build: true, + launch: true, + cache: true, + }, + &|_| panic!("invalid_metadata callback should not be called!"), + &|metadata, path| { + assert_eq!(metadata, &Some(toml! { answer = 42 })); + assert_eq!(path, temp_dir.path().join(&*layer_name.clone())); + (InspectExistingAction::DeleteLayer, DELETE_CAUSE) + }, + &layer_name, + temp_dir.path(), + ) + .unwrap(); + + assert_eq!(layer_ref.path(), temp_dir.path().join(&*layer_name)); + assert!(layer_ref.path().is_dir()); + assert_eq!( + read_toml_file::>( + temp_dir.path().join(format!("{layer_name}.toml")) + ) + .unwrap(), + LayerContentMetadata { + types: Some(LayerTypes { + build: true, + launch: true, + cache: true, + }), + metadata: GenericMetadata::default() + } + ); + assert_eq!( + layer_ref.contents, + LayerContents::Empty { + cause: EmptyLayerCause::Inspect { + cause: DELETE_CAUSE + } + } + ); + } + + #[test] + fn handle_layer_cached_invalid_metadata_delete() { + const DELETE_CAUSE: &str = "cause"; + + #[derive(Serialize, Deserialize)] + struct TestLayerMetadata { + planet: String, + } + + let temp_dir = tempdir().unwrap(); + let layer_name = layer_name!("test_layer"); + + // Create a layer as if it was restored by the CNB lifecyle, most notably WITHOUT layer + // types but WITH metadata. + std::fs::create_dir_all(temp_dir.path().join(&*layer_name)).unwrap(); + std::fs::write( + temp_dir.path().join(format!("{layer_name}.toml")), + "[metadata]\nanswer=42", + ) + .unwrap(); + + let layer_ref = handle_layer::< + TestBuildpack, + TestLayerMetadata, + _, + (InspectExistingAction, &str), + &str, + _, + >( + LayerTypes { + build: true, + launch: true, + cache: true, + }, + &|metadata| { + assert_eq!(metadata, &Some(toml! { answer = 42 })); + (InvalidMetadataAction::DeleteLayer, DELETE_CAUSE) + }, + &|_, _| panic!("inspect_existing callback should not be called!"), + &layer_name, + temp_dir.path(), + ) + .unwrap(); + + assert_eq!(layer_ref.path(), temp_dir.path().join(&*layer_name)); + assert!(layer_ref.path().is_dir()); + assert_eq!( + read_toml_file::>( + temp_dir.path().join(format!("{layer_name}.toml")) + ) + .unwrap(), + LayerContentMetadata { + types: Some(LayerTypes { + build: true, + launch: true, + cache: true, + }), + metadata: GenericMetadata::default() + } + ); + assert_eq!( + layer_ref.contents, + LayerContents::Empty { + cause: EmptyLayerCause::MetadataInvalid { + cause: DELETE_CAUSE + } + } + ); + } + + #[test] + fn handle_layer_cached_invalid_metadata_replace() { + const KEEP_CAUSE: &str = "cause"; + + #[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] + struct TestLayerMetadata { + planet: String, + } + + let temp_dir = tempdir().unwrap(); + let layer_name = layer_name!("test_layer"); + + // Create a layer as if it was restored by the CNB lifecyle, most notably WITHOUT layer + // types but WITH metadata. + std::fs::create_dir_all(temp_dir.path().join(&*layer_name)).unwrap(); + std::fs::write( + temp_dir.path().join(&*layer_name).join("data.txt"), + "some_data", + ) + .unwrap(); + std::fs::write( + temp_dir.path().join(format!("{layer_name}.toml")), + "[metadata]\nanswer=42", + ) + .unwrap(); + + let layer_ref = handle_layer::( + LayerTypes { + build: true, + launch: true, + cache: true, + }, + &|metadata| { + assert_eq!(metadata, &Some(toml! { answer = 42 })); + + InvalidMetadataAction::ReplaceMetadata(TestLayerMetadata { + planet: String::from("LV-246"), + }) + }, + &|metadata, _| { + assert_eq!( + metadata, + &TestLayerMetadata { + planet: String::from("LV-246"), + } + ); + + (InspectExistingAction::KeepLayer, KEEP_CAUSE) + }, + &layer_name, + temp_dir.path(), + ) + .unwrap(); + + assert_eq!(layer_ref.path(), temp_dir.path().join(&*layer_name)); + assert!(layer_ref.path().is_dir()); + assert_eq!( + read_toml_file::>( + temp_dir.path().join(format!("{layer_name}.toml")) + ) + .unwrap(), + LayerContentMetadata { + types: Some(LayerTypes { + build: true, + launch: true, + cache: true, + }), + metadata: TestLayerMetadata { + planet: String::from("LV-246") + } + } + ); + + assert_eq!( + layer_ref.contents, + LayerContents::Cached { cause: KEEP_CAUSE } + ); + } + + struct TestBuildpack; + impl Buildpack for TestBuildpack { + type Platform = GenericPlatform; + type Metadata = GenericMetadata; + type Error = GenericError; + + fn detect(&self, _: DetectContext) -> crate::Result { + unimplemented!() + } + + fn build(&self, _: BuildContext) -> crate::Result { + unimplemented!() + } + } +} diff --git a/libcnb/src/layer/struct_api/mod.rs b/libcnb/src/layer/struct_api/mod.rs index 27009abe..8518cd81 100644 --- a/libcnb/src/layer/struct_api/mod.rs +++ b/libcnb/src/layer/struct_api/mod.rs @@ -66,7 +66,7 @@ pub enum InspectExistingAction { /// Framework metadata about the layer contents. /// /// See: [`BuildContext::cached_layer`] and [`BuildContext::uncached_layer`] -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum LayerContents { /// The layer contains validated cached contents from a previous buildpack run. /// @@ -77,7 +77,7 @@ pub enum LayerContents { } /// The cause of a layer being empty. -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum EmptyLayerCause { /// The layer wasn't cached in a previous buildpack run and was freshly created. Uncached, From fc818894ed725c04337f4cf6e0f488d9efa8b0d0 Mon Sep 17 00:00:00 2001 From: Manuel Fuchs Date: Fri, 14 Jun 2024 13:44:24 +0200 Subject: [PATCH 16/29] Rename LayerContents to LayerState, InspectExisting to InspectRestored --- libcnb/src/build.rs | 44 +++++++-------- libcnb/src/layer/shared.rs | 2 +- libcnb/src/layer/struct_api/handling.rs | 72 +++++++++++-------------- libcnb/src/layer/struct_api/mod.rs | 48 ++++++++--------- 4 files changed, 79 insertions(+), 87 deletions(-) diff --git a/libcnb/src/build.rs b/libcnb/src/build.rs index 896f3714..5c9fd16d 100644 --- a/libcnb/src/build.rs +++ b/libcnb/src/build.rs @@ -8,7 +8,7 @@ use crate::data::{ }; use crate::layer::trait_api::handling::LayerErrorOrBuildpackError; use crate::layer::{ - CachedLayerDefinition, InspectExistingAction, IntoAction, InvalidMetadataAction, LayerRef, + CachedLayerDefinition, InspectRestoredAction, IntoAction, InvalidMetadataAction, LayerRef, UncachedLayerDefinition, }; use crate::sbom::Sbom; @@ -137,7 +137,7 @@ impl BuildContext { /// # use libcnb::detect::{DetectContext, DetectResult}; /// # use libcnb::generic::GenericPlatform; /// # use libcnb::layer::{ - /// # CachedLayerDefinition, InspectExistingAction, InvalidMetadataAction, LayerContents, + /// # CachedLayerDefinition, InspectRestoredAction, InvalidMetadataAction, LayerState, /// # }; /// # use libcnb::layer_env::{LayerEnv, ModificationBehavior, Scope}; /// # use libcnb::Buildpack; @@ -175,7 +175,7 @@ impl BuildContext { /// // inspect the contents and metadata to decide if we want to keep the existing /// // layer or let libcnb delete the existing layer and create a new one for us. /// // This is libcnb's method to implement cache invalidations for layers. - /// inspect_existing: &|_: &GenericMetadata, _| InspectExistingAction::KeepLayer, + /// inspect_restored: &|_: &GenericMetadata, _| InspectRestoredAction::KeepLayer, /// }, /// )?; /// @@ -187,8 +187,8 @@ impl BuildContext { /// // In the majority of cases, we don't need more details beyond if it's empty or not and can /// // ignore the details. This is what we do in this example. See the later example for a more /// // complex situation. - /// match layer_ref.contents { - /// LayerContents::Empty { .. } => { + /// match layer_ref.state { + /// LayerState::Empty { .. } => { /// println!("Creating new example layer!"); /// /// // Modify the layer contents with regular Rust functions: @@ -206,7 +206,7 @@ impl BuildContext { /// "LV-246", /// ))?; /// } - /// LayerContents::Cached { .. } => { + /// LayerState::Restored { .. } => { /// println!("Reusing example layer from previous run!"); /// } /// } @@ -228,8 +228,8 @@ impl BuildContext { /// # use libcnb::detect::{DetectContext, DetectResult}; /// # use libcnb::generic::GenericPlatform; /// # use libcnb::layer::{ - /// # CachedLayerDefinition, EmptyLayerCause, InspectExistingAction, InvalidMetadataAction, - /// # LayerContents, + /// # CachedLayerDefinition, EmptyLayerCause, InspectRestoredAction, InvalidMetadataAction, + /// # LayerState, /// # }; /// # use libcnb::Buildpack; /// # use libcnb_data::generic::GenericMetadata; @@ -272,10 +272,10 @@ impl BuildContext { /// build: false, /// launch: false, /// invalid_metadata: &|_| InvalidMetadataAction::DeleteLayer, - /// inspect_existing: &|metadata: &ExampleLayerMetadata, layer_dir| { + /// inspect_restored: &|metadata: &ExampleLayerMetadata, layer_dir| { /// if metadata.lang_runtime_version.starts_with("0.") { /// Ok(( - /// InspectExistingAction::DeleteLayer, + /// InspectRestoredAction::DeleteLayer, /// CustomCause::LegacyVersion, /// )) /// } else { @@ -291,15 +291,15 @@ impl BuildContext { /// /// if file_contents == "known-broken-0.1c" { /// Ok(( - /// InspectExistingAction::DeleteLayer, + /// InspectRestoredAction::DeleteLayer, /// CustomCause::HasBrokenModule, /// )) /// } else { - /// Ok((InspectExistingAction::KeepLayer, CustomCause::Ok)) + /// Ok((InspectRestoredAction::KeepLayer, CustomCause::Ok)) /// } /// } else { /// Ok(( - /// InspectExistingAction::DeleteLayer, + /// InspectRestoredAction::DeleteLayer, /// CustomCause::MissingModulesFile, /// )) /// } @@ -308,8 +308,8 @@ impl BuildContext { /// }, /// )?; /// - /// match layer_ref.contents { - /// LayerContents::Empty { ref cause } => { + /// match layer_ref.state { + /// LayerState::Empty { ref cause } => { /// // Since the cause is just a regular Rust value, we can match it with regular /// // Rust syntax and be as complex or simple as we need. /// let message = match cause { @@ -330,7 +330,7 @@ impl BuildContext { /// lang_runtime_version: String::from("1.0.0"), /// })?; /// } - /// LayerContents::Cached { .. } => { + /// LayerState::Restored { .. } => { /// println!("Re-using cached language runtime"); /// } /// } @@ -345,15 +345,15 @@ impl BuildContext { /// # } /// # } /// ``` - pub fn cached_layer<'a, M, MA, EA, MAC, EAC>( + pub fn cached_layer<'a, M, MA, IA, MAC, IAC>( &self, layer_name: impl Borrow, - layer_definition: impl Borrow>, - ) -> crate::Result, B::Error> + layer_definition: impl Borrow>, + ) -> crate::Result, B::Error> where M: 'a + Serialize + DeserializeOwned, MA: 'a + IntoAction, MAC, B::Error>, - EA: 'a + IntoAction, + IA: 'a + IntoAction, { let layer_definition = layer_definition.borrow(); @@ -364,7 +364,7 @@ impl BuildContext { cache: true, }, layer_definition.invalid_metadata, - layer_definition.inspect_existing, + layer_definition.inspect_restored, layer_name.borrow(), &self.layers_dir, ) @@ -390,7 +390,7 @@ impl BuildContext { cache: false, }, &|_| InvalidMetadataAction::DeleteLayer, - &|_: &GenericMetadata, _| InspectExistingAction::DeleteLayer, + &|_: &GenericMetadata, _| InspectRestoredAction::DeleteLayer, layer_name.borrow(), &self.layers_dir, ) diff --git a/libcnb/src/layer/shared.rs b/libcnb/src/layer/shared.rs index 46158f9c..cb6ea69a 100644 --- a/libcnb/src/layer/shared.rs +++ b/libcnb/src/layer/shared.rs @@ -121,7 +121,7 @@ pub(in crate::layer) fn delete_layer>( #[derive(thiserror::Error, Debug)] pub enum DeleteLayerError { - #[error("I/O error while deleting existing layer: {0}")] + #[error("I/O error while deleting layer: {0}")] IoError(#[from] std::io::Error), } diff --git a/libcnb/src/layer/struct_api/handling.rs b/libcnb/src/layer/struct_api/handling.rs index aaf127cd..97a8d4e1 100644 --- a/libcnb/src/layer/struct_api/handling.rs +++ b/libcnb/src/layer/struct_api/handling.rs @@ -3,8 +3,8 @@ use crate::layer::shared::{ WriteLayerError, }; use crate::layer::{ - EmptyLayerCause, InspectExistingAction, IntoAction, InvalidMetadataAction, LayerContents, - LayerError, LayerRef, + EmptyLayerCause, InspectRestoredAction, IntoAction, InvalidMetadataAction, LayerError, + LayerRef, LayerState, }; use crate::Buildpack; use libcnb_common::toml_file::read_toml_file; @@ -16,18 +16,18 @@ use serde::Serialize; use std::marker::PhantomData; use std::path::{Path, PathBuf}; -pub(crate) fn handle_layer( +pub(crate) fn handle_layer( layer_types: LayerTypes, invalid_metadata: &dyn Fn(&GenericMetadata) -> MA, - inspect_existing: &dyn Fn(&M, &Path) -> EA, + inspect_restored: &dyn Fn(&M, &Path) -> IA, layer_name: &LayerName, layers_dir: &Path, -) -> crate::Result, B::Error> +) -> crate::Result, B::Error> where B: Buildpack + ?Sized, M: Serialize + DeserializeOwned, MA: IntoAction, MAC, B::Error>, - EA: IntoAction, + IA: IntoAction, { match read_layer::(layers_dir, layer_name) { Ok(None) => create_layer( @@ -37,12 +37,12 @@ where EmptyLayerCause::Uncached, ), Ok(Some(layer_data)) => { - let inspect_action = inspect_existing(&layer_data.metadata.metadata, &layer_data.path) + let inspect_action = inspect_restored(&layer_data.metadata.metadata, &layer_data.path) .into_action() .map_err(crate::Error::BuildpackError)?; match inspect_action { - (InspectExistingAction::DeleteLayer, cause) => { + (InspectRestoredAction::DeleteLayer, cause) => { delete_layer(layers_dir, layer_name).map_err(LayerError::DeleteLayerError)?; create_layer( @@ -52,7 +52,7 @@ where EmptyLayerCause::Inspect { cause }, ) } - (InspectExistingAction::KeepLayer, cause) => { + (InspectRestoredAction::KeepLayer, cause) => { // Always write the layer types as: // a) they might be different from what is currently on disk // b) the cache field will be removed by CNB lifecycle on cache restore @@ -64,7 +64,7 @@ where name: layer_data.name, layers_dir: PathBuf::from(layers_dir), buildpack: PhantomData, - contents: LayerContents::Cached { cause }, + state: LayerState::Restored { cause }, }) } } @@ -98,7 +98,7 @@ where handle_layer( layer_types, invalid_metadata, - inspect_existing, + inspect_restored, layer_name, layers_dir, ) @@ -109,12 +109,12 @@ where } } -fn create_layer( +fn create_layer( layer_types: LayerTypes, layer_name: &LayerName, layers_dir: &Path, - empty_layer_cause: EmptyLayerCause, -) -> Result, crate::Error> + empty_layer_cause: EmptyLayerCause, +) -> Result, crate::Error> where B: Buildpack + ?Sized, { @@ -136,7 +136,7 @@ where name: layer_data.name, layers_dir: PathBuf::from(layers_dir), buildpack: PhantomData, - contents: LayerContents::Empty { + state: LayerState::Empty { cause: empty_layer_cause, }, }) @@ -148,9 +148,7 @@ mod tests { use crate::build::{BuildContext, BuildResult}; use crate::detect::{DetectContext, DetectResult}; use crate::generic::{GenericError, GenericPlatform}; - use crate::layer::{ - EmptyLayerCause, InspectExistingAction, InvalidMetadataAction, LayerContents, - }; + use crate::layer::{EmptyLayerCause, InspectRestoredAction, InvalidMetadataAction, LayerState}; use crate::Buildpack; use libcnb_common::toml_file::read_toml_file; use libcnb_data::generic::GenericMetadata; @@ -179,7 +177,7 @@ mod tests { .unwrap(); assert_eq!(layer_ref.layers_dir, temp_dir.path()); - assert_eq!(layer_ref.contents, LayerContents::Empty { cause }); + assert_eq!(layer_ref.state, LayerState::Empty { cause }); assert!(temp_dir.path().join(&*layer_name).is_dir()); assert_eq!( read_toml_file::>( @@ -206,7 +204,7 @@ mod tests { TestBuildpack, GenericMetadata, InvalidMetadataAction, - InspectExistingAction, + InspectRestoredAction, (), (), >( @@ -216,7 +214,7 @@ mod tests { cache: true, }, &|_| panic!("invalid_metadata callback should not be called!"), - &|_, _| panic!("inspect_existing callback should not be called!"), + &|_, _| panic!("inspect_restored callback should not be called!"), &layer_name, temp_dir.path(), ) @@ -239,8 +237,8 @@ mod tests { } ); assert_eq!( - layer_ref.contents, - LayerContents::Empty { + layer_ref.state, + LayerState::Empty { cause: EmptyLayerCause::Uncached } ); @@ -273,7 +271,7 @@ mod tests { &|metadata, path| { assert_eq!(metadata, &Some(toml! { answer = 42 })); assert_eq!(path, temp_dir.path().join(&*layer_name.clone())); - (InspectExistingAction::KeepLayer, KEEP_CAUSE) + (InspectRestoredAction::KeepLayer, KEEP_CAUSE) }, &layer_name, temp_dir.path(), @@ -296,10 +294,7 @@ mod tests { metadata: Some(toml! { answer = 42 }) } ); - assert_eq!( - layer_ref.contents, - LayerContents::Cached { cause: KEEP_CAUSE } - ); + assert_eq!(layer_ref.state, LayerState::Restored { cause: KEEP_CAUSE }); } #[test] @@ -329,7 +324,7 @@ mod tests { &|metadata, path| { assert_eq!(metadata, &Some(toml! { answer = 42 })); assert_eq!(path, temp_dir.path().join(&*layer_name.clone())); - (InspectExistingAction::DeleteLayer, DELETE_CAUSE) + (InspectRestoredAction::DeleteLayer, DELETE_CAUSE) }, &layer_name, temp_dir.path(), @@ -353,8 +348,8 @@ mod tests { } ); assert_eq!( - layer_ref.contents, - LayerContents::Empty { + layer_ref.state, + LayerState::Empty { cause: EmptyLayerCause::Inspect { cause: DELETE_CAUSE } @@ -387,7 +382,7 @@ mod tests { TestBuildpack, TestLayerMetadata, _, - (InspectExistingAction, &str), + (InspectRestoredAction, &str), &str, _, >( @@ -400,7 +395,7 @@ mod tests { assert_eq!(metadata, &Some(toml! { answer = 42 })); (InvalidMetadataAction::DeleteLayer, DELETE_CAUSE) }, - &|_, _| panic!("inspect_existing callback should not be called!"), + &|_, _| panic!("inspect_restored callback should not be called!"), &layer_name, temp_dir.path(), ) @@ -423,8 +418,8 @@ mod tests { } ); assert_eq!( - layer_ref.contents, - LayerContents::Empty { + layer_ref.state, + LayerState::Empty { cause: EmptyLayerCause::MetadataInvalid { cause: DELETE_CAUSE } @@ -479,7 +474,7 @@ mod tests { } ); - (InspectExistingAction::KeepLayer, KEEP_CAUSE) + (InspectRestoredAction::KeepLayer, KEEP_CAUSE) }, &layer_name, temp_dir.path(), @@ -505,10 +500,7 @@ mod tests { } ); - assert_eq!( - layer_ref.contents, - LayerContents::Cached { cause: KEEP_CAUSE } - ); + assert_eq!(layer_ref.state, LayerState::Restored { cause: KEEP_CAUSE }); } struct TestBuildpack; diff --git a/libcnb/src/layer/struct_api/mod.rs b/libcnb/src/layer/struct_api/mod.rs index 8518cd81..4574f85c 100644 --- a/libcnb/src/layer/struct_api/mod.rs +++ b/libcnb/src/layer/struct_api/mod.rs @@ -19,19 +19,19 @@ use std::path::{Path, PathBuf}; /// A definition for a cached layer. /// /// Refer to the docs of [`BuildContext::cached_layer`] for usage examples. -pub struct CachedLayerDefinition<'a, M, MA, EA> { +pub struct CachedLayerDefinition<'a, M, MA, IA> { /// Whether the layer is intended for build. pub build: bool, /// Whether the layer is intended for launch. pub launch: bool, - /// Callback for when the metadata of an existing layer cannot be parsed as `M`. + /// Callback for when the metadata of a restored layer cannot be parsed as `M`. /// /// Allows replacing the metadata before continuing (i.e. migration to a newer version) or /// deleting the layer. pub invalid_metadata: &'a dyn Fn(&GenericMetadata) -> MA, - /// Callback when the layer already exists to validate the contents and metadata. Can be used - /// to delete existing cached layers. - pub inspect_existing: &'a dyn Fn(&M, &Path) -> EA, + /// Callback when the layer was restored from cache to validate the contents and metadata. + /// Can be used to delete existing cached layers. + pub inspect_restored: &'a dyn Fn(&M, &Path) -> IA, } /// A definition for an uncached layer. @@ -54,31 +54,31 @@ pub enum InvalidMetadataAction { ReplaceMetadata(M), } -/// The action to take after inspecting existing layer data. +/// The action to take after inspecting restored layer data. #[derive(Copy, Clone, Debug)] -pub enum InspectExistingAction { - /// Delete the existing layer. +pub enum InspectRestoredAction { + /// Delete the restored layer. DeleteLayer, /// Keep the layer as-is. KeepLayer, } -/// Framework metadata about the layer contents. +/// Framework metadata about the layer state. /// /// See: [`BuildContext::cached_layer`] and [`BuildContext::uncached_layer`] #[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub enum LayerContents { +pub enum LayerState { /// The layer contains validated cached contents from a previous buildpack run. /// - /// See: `inspect_existing` in [`CachedLayerDefinition`]. - Cached { cause: EAC }, + /// See: `inspect_restored` in [`CachedLayerDefinition`]. + Restored { cause: IAC }, /// The layer is empty. Inspect the contained [`EmptyLayerCause`] for the cause. - Empty { cause: EmptyLayerCause }, + Empty { cause: EmptyLayerCause }, } /// The cause of a layer being empty. #[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub enum EmptyLayerCause { +pub enum EmptyLayerCause { /// The layer wasn't cached in a previous buildpack run and was freshly created. Uncached, /// The layer was cached in a previous buildpack run, but the metadata was invalid and couldn't @@ -86,11 +86,11 @@ pub enum EmptyLayerCause { /// /// See: `invalid_metadata` in [`CachedLayerDefinition`]. MetadataInvalid { cause: MAC }, - /// The layer was cached in a previous buildpack run, but the `inspect_existing` function + /// The layer was cached in a previous buildpack run, but the `inspect_restored` function /// rejected the contents and/or metadata. /// - /// See: `inspect_existing` in [`CachedLayerDefinition`]. - Inspect { cause: EAC }, + /// See: `inspect_restored` in [`CachedLayerDefinition`]. + Inspect { cause: IAC }, } /// A value-to-value conversion for layer actions. @@ -101,10 +101,10 @@ pub enum EmptyLayerCause { /// Implement this trait if you want to use your own types as actions. /// /// libcnb ships with generic implementations for the majority of the use-cases: -/// - Using [`InspectExistingAction`] or [`InvalidMetadataAction`] directly. -/// - Using [`InspectExistingAction`] or [`InvalidMetadataAction`] directly, wrapped in a Result. -/// - Using [`InspectExistingAction`] or [`InvalidMetadataAction`] with a cause value in a tuple. -/// - Using [`InspectExistingAction`] or [`InvalidMetadataAction`] with a cause value in a tuple, wrapped in a Result. +/// - Using [`InspectRestoredAction`] or [`InvalidMetadataAction`] directly. +/// - Using [`InspectRestoredAction`] or [`InvalidMetadataAction`] directly, wrapped in a Result. +/// - Using [`InspectRestoredAction`] or [`InvalidMetadataAction`] with a cause value in a tuple. +/// - Using [`InspectRestoredAction`] or [`InvalidMetadataAction`] with a cause value in a tuple, wrapped in a Result. pub trait IntoAction { fn into_action(self) -> Result<(T, C), E>; } @@ -143,7 +143,7 @@ impl IntoAction for Result<(T, C), E> { /// exec.d programs. /// /// To obtain a such a reference, use [`BuildContext::cached_layer`] or [`BuildContext::uncached_layer`]. -pub struct LayerRef +pub struct LayerRef where B: Buildpack + ?Sized, { @@ -154,10 +154,10 @@ where // the layers_dir here. layers_dir: PathBuf, buildpack: PhantomData, - pub contents: LayerContents, + pub state: LayerState, } -impl LayerRef +impl LayerRef where B: Buildpack, { From 21ad86b869c4b541a2a122f9bec962341aff52ff Mon Sep 17 00:00:00 2001 From: Manuel Fuchs Date: Fri, 14 Jun 2024 14:46:25 +0200 Subject: [PATCH 17/29] Update CHANGELOG --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e7abc18..463f96c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- `libcnb`: + - A new API for working with layers as been added. Check `BuildContext::cached_layer` for a starting point how to work with this API. ([#814](https://github.com/heroku/libcnb.rs/pull/814)) + +### Changed + +- `libcnb`: + - The `Layer` trait and related types and functions have been deprecated. Please migrate to the new API. ([#814](https://github.com/heroku/libcnb.rs/pull/814)) + - Errors related to layers have been restructured. While this is technically a breaking change, buildpacks usually don't have to be modified in practice. ([#814](https://github.com/heroku/libcnb.rs/pull/814)) + ### Fixed - `libcnb-data`: From c900b88f8e3cfbeb65031f892201f10e772f416e Mon Sep 17 00:00:00 2001 From: Manuel Fuchs Date: Mon, 17 Jun 2024 18:37:48 +0200 Subject: [PATCH 18/29] Fix typos --- libcnb/src/build.rs | 4 ++-- libcnb/src/layer/struct_api/handling.rs | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/libcnb/src/build.rs b/libcnb/src/build.rs index 5c9fd16d..d937b1e9 100644 --- a/libcnb/src/build.rs +++ b/libcnb/src/build.rs @@ -128,7 +128,7 @@ impl BuildContext { /// /// Users of this function pass in a [`CachedLayerDefinition`] that describes the desired layer /// and the returned `LayerRef` can then be used to modify the layer like any other path. This - /// allows users to be flexible when and how the layer is modified and to abstract layer + /// allows users to be flexible in how and when the layer is modified and to abstract layer /// creation away if necessary. /// /// # Basic Example @@ -181,7 +181,7 @@ impl BuildContext { /// /// // At this point, a layer exists on disk. It might contain cached data or might be empty. /// // Since we need to conditionally work with the layer contents based on its state, we can - /// // inspect the `contents` field of the layer reference to get detailed information about + /// // inspect the `state` field of the layer reference to get detailed information about /// // the current layer contents and the cause(s) for the state. /// // /// // In the majority of cases, we don't need more details beyond if it's empty or not and can diff --git a/libcnb/src/layer/struct_api/handling.rs b/libcnb/src/layer/struct_api/handling.rs index 97a8d4e1..3293e024 100644 --- a/libcnb/src/layer/struct_api/handling.rs +++ b/libcnb/src/layer/struct_api/handling.rs @@ -251,7 +251,7 @@ mod tests { let temp_dir = tempdir().unwrap(); let layer_name = layer_name!("test_layer"); - // Create a layer as if it was restored by the CNB lifecyle, most notably WITHOUT layer + // Create a layer as if it was restored by the CNB lifecycle, most notably WITHOUT layer // types but WITH metadata. std::fs::create_dir_all(temp_dir.path().join(&*layer_name)).unwrap(); std::fs::write( @@ -304,7 +304,7 @@ mod tests { let temp_dir = tempdir().unwrap(); let layer_name = layer_name!("test_layer"); - // Create a layer as if it was restored by the CNB lifecyle, most notably WITHOUT layer + // Create a layer as if it was restored by the CNB lifecycle, most notably WITHOUT layer // types but WITH metadata. std::fs::create_dir_all(temp_dir.path().join(&*layer_name)).unwrap(); std::fs::write( @@ -369,7 +369,7 @@ mod tests { let temp_dir = tempdir().unwrap(); let layer_name = layer_name!("test_layer"); - // Create a layer as if it was restored by the CNB lifecyle, most notably WITHOUT layer + // Create a layer as if it was restored by the CNB lifecycle, most notably WITHOUT layer // types but WITH metadata. std::fs::create_dir_all(temp_dir.path().join(&*layer_name)).unwrap(); std::fs::write( @@ -439,7 +439,7 @@ mod tests { let temp_dir = tempdir().unwrap(); let layer_name = layer_name!("test_layer"); - // Create a layer as if it was restored by the CNB lifecyle, most notably WITHOUT layer + // Create a layer as if it was restored by the CNB lifecycle, most notably WITHOUT layer // types but WITH metadata. std::fs::create_dir_all(temp_dir.path().join(&*layer_name)).unwrap(); std::fs::write( From de6351bf835cb0e7935b8b5287b08faf7360720b Mon Sep 17 00:00:00 2001 From: Manuel Fuchs Date: Mon, 17 Jun 2024 18:40:54 +0200 Subject: [PATCH 19/29] Fix CHANGELOG entry --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 463f96c3..164077e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - `libcnb`: - - A new API for working with layers as been added. Check `BuildContext::cached_layer` for a starting point how to work with this API. ([#814](https://github.com/heroku/libcnb.rs/pull/814)) + - A new API for working with layers has been added. See the `BuildContext::cached_layer` and `BuildContext::uncached_layer` docs for examples of how to use this API. ([#814](https://github.com/heroku/libcnb.rs/pull/814)) ### Changed From 06d466876f9140a27e2eef25cd79726eb4e01223 Mon Sep 17 00:00:00 2001 From: Manuel Fuchs Date: Mon, 17 Jun 2024 18:47:45 +0200 Subject: [PATCH 20/29] Rename LayerRef methods --- libcnb/src/build.rs | 4 ++-- libcnb/src/layer/struct_api/mod.rs | 25 ++++++++++++++++--------- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/libcnb/src/build.rs b/libcnb/src/build.rs index d937b1e9..6c85f9e0 100644 --- a/libcnb/src/build.rs +++ b/libcnb/src/build.rs @@ -199,7 +199,7 @@ impl BuildContext { /// .map_err(ExampleBuildpackError::WriteDataError)?; /// /// // Use functions on LayerRef for common CNB specific layer modifications: - /// layer_ref.replace_env(LayerEnv::new().chainable_insert( + /// layer_ref.write_env(LayerEnv::new().chainable_insert( /// Scope::All, /// ModificationBehavior::Append, /// "PLANET", @@ -326,7 +326,7 @@ impl BuildContext { /// /// // Code to install the language runtime would go here /// - /// layer_ref.replace_metadata(ExampleLayerMetadata { + /// layer_ref.write_metadata(ExampleLayerMetadata { /// lang_runtime_version: String::from("1.0.0"), /// })?; /// } diff --git a/libcnb/src/layer/struct_api/mod.rs b/libcnb/src/layer/struct_api/mod.rs index 4574f85c..3e609954 100644 --- a/libcnb/src/layer/struct_api/mod.rs +++ b/libcnb/src/layer/struct_api/mod.rs @@ -166,10 +166,11 @@ where self.layers_dir.join(self.name.as_str()) } - /// Replaces the existing layer metadata with a new value. + /// Writes the given layer metadata to disk. /// - /// The new value does not have to be of the same type as the existing metadata. - pub fn replace_metadata(&self, metadata: M) -> crate::Result<(), B::Error> + /// Any existing layer metadata will be overwritten. The new value does not have to be of the + /// same type as the existing metadata. + pub fn write_metadata(&self, metadata: M) -> crate::Result<(), B::Error> where M: Serialize, { @@ -181,8 +182,10 @@ where }) } - /// Replaces the existing layer environment with a new one. - pub fn replace_env(&self, env: impl Borrow) -> crate::Result<(), B::Error> { + /// Writes the given layer environment to disk. + /// + /// Any existing layer environment will be overwritten. + pub fn write_env(&self, env: impl Borrow) -> crate::Result<(), B::Error> { env.borrow() .write_to_layer_dir(self.path()) .map_err(|error| { @@ -192,8 +195,10 @@ where }) } - /// Replace all existing layer SBOMs with new ones. - pub fn replace_sboms(&self, sboms: &[Sbom]) -> crate::Result<(), B::Error> { + /// Writes the given SBOMs to disk. + /// + /// Any existing SBOMs will be overwritten. + pub fn write_sboms(&self, sboms: &[Sbom]) -> crate::Result<(), B::Error> { replace_layer_sboms(&self.layers_dir, &self.name, sboms).map_err(|error| { crate::Error::LayerError(LayerError::WriteLayerError( WriteLayerError::ReplaceLayerSbomsError(error), @@ -201,8 +206,10 @@ where }) } - /// Replace all existing layer exec.d programs with new ones. - pub fn replace_exec_d_programs(&self, programs: P) -> crate::Result<(), B::Error> + /// Writes the given exec.d programs to disk. + /// + /// Any existing exec.d programs will be overwritten. + pub fn write_exec_d_programs(&self, programs: P) -> crate::Result<(), B::Error> where S: Into, P: IntoIterator, From bfcb0cec49789849f912c27ac418fe64e78be8a0 Mon Sep 17 00:00:00 2001 From: Manuel Fuchs Date: Mon, 17 Jun 2024 18:50:26 +0200 Subject: [PATCH 21/29] Rename CachedLayerDefinition callbacks --- libcnb/src/build.rs | 36 +++++++------- libcnb/src/layer/struct_api/handling.rs | 64 +++++++++++++------------ libcnb/src/layer/struct_api/mod.rs | 40 ++++++++-------- 3 files changed, 71 insertions(+), 69 deletions(-) diff --git a/libcnb/src/build.rs b/libcnb/src/build.rs index 6c85f9e0..e61b2864 100644 --- a/libcnb/src/build.rs +++ b/libcnb/src/build.rs @@ -8,7 +8,7 @@ use crate::data::{ }; use crate::layer::trait_api::handling::LayerErrorOrBuildpackError; use crate::layer::{ - CachedLayerDefinition, InspectRestoredAction, IntoAction, InvalidMetadataAction, LayerRef, + CachedLayerDefinition, IntoAction, InvalidMetadataAction, LayerRef, RestoredLayerAction, UncachedLayerDefinition, }; use crate::sbom::Sbom; @@ -137,7 +137,7 @@ impl BuildContext { /// # use libcnb::detect::{DetectContext, DetectResult}; /// # use libcnb::generic::GenericPlatform; /// # use libcnb::layer::{ - /// # CachedLayerDefinition, InspectRestoredAction, InvalidMetadataAction, LayerState, + /// # CachedLayerDefinition, RestoredLayerAction, InvalidMetadataAction, LayerState, /// # }; /// # use libcnb::layer_env::{LayerEnv, ModificationBehavior, Scope}; /// # use libcnb::Buildpack; @@ -170,12 +170,12 @@ impl BuildContext { /// // Will be called if a cached version of the layer was found, but the metadata /// // could not be parsed. In this example, we instruct libcnb to always delete the /// // existing layer in such a case. But we can implement any logic here if we want. - /// invalid_metadata: &|_| InvalidMetadataAction::DeleteLayer, + /// invalid_metadata_action: &|_| InvalidMetadataAction::DeleteLayer, /// // Will be called if a cached version of the layer was found. This allows us to /// // inspect the contents and metadata to decide if we want to keep the existing /// // layer or let libcnb delete the existing layer and create a new one for us. /// // This is libcnb's method to implement cache invalidations for layers. - /// inspect_restored: &|_: &GenericMetadata, _| InspectRestoredAction::KeepLayer, + /// restored_layer_action: &|_: &GenericMetadata, _| RestoredLayerAction::KeepLayer, /// }, /// )?; /// @@ -228,7 +228,7 @@ impl BuildContext { /// # use libcnb::detect::{DetectContext, DetectResult}; /// # use libcnb::generic::GenericPlatform; /// # use libcnb::layer::{ - /// # CachedLayerDefinition, EmptyLayerCause, InspectRestoredAction, InvalidMetadataAction, + /// # CachedLayerDefinition, EmptyLayerCause, RestoredLayerAction, InvalidMetadataAction, /// # LayerState, /// # }; /// # use libcnb::Buildpack; @@ -271,11 +271,11 @@ impl BuildContext { /// CachedLayerDefinition { /// build: false, /// launch: false, - /// invalid_metadata: &|_| InvalidMetadataAction::DeleteLayer, - /// inspect_restored: &|metadata: &ExampleLayerMetadata, layer_dir| { + /// invalid_metadata_action: &|_| InvalidMetadataAction::DeleteLayer, + /// restored_layer_action: &|metadata: &ExampleLayerMetadata, layer_dir| { /// if metadata.lang_runtime_version.starts_with("0.") { /// Ok(( - /// InspectRestoredAction::DeleteLayer, + /// RestoredLayerAction::DeleteLayer, /// CustomCause::LegacyVersion, /// )) /// } else { @@ -291,15 +291,15 @@ impl BuildContext { /// /// if file_contents == "known-broken-0.1c" { /// Ok(( - /// InspectRestoredAction::DeleteLayer, + /// RestoredLayerAction::DeleteLayer, /// CustomCause::HasBrokenModule, /// )) /// } else { - /// Ok((InspectRestoredAction::KeepLayer, CustomCause::Ok)) + /// Ok((RestoredLayerAction::KeepLayer, CustomCause::Ok)) /// } /// } else { /// Ok(( - /// InspectRestoredAction::DeleteLayer, + /// RestoredLayerAction::DeleteLayer, /// CustomCause::MissingModulesFile, /// )) /// } @@ -345,15 +345,15 @@ impl BuildContext { /// # } /// # } /// ``` - pub fn cached_layer<'a, M, MA, IA, MAC, IAC>( + pub fn cached_layer<'a, M, MA, RA, MAC, RAC>( &self, layer_name: impl Borrow, - layer_definition: impl Borrow>, - ) -> crate::Result, B::Error> + layer_definition: impl Borrow>, + ) -> crate::Result, B::Error> where M: 'a + Serialize + DeserializeOwned, MA: 'a + IntoAction, MAC, B::Error>, - IA: 'a + IntoAction, + RA: 'a + IntoAction, { let layer_definition = layer_definition.borrow(); @@ -363,8 +363,8 @@ impl BuildContext { build: layer_definition.build, cache: true, }, - layer_definition.invalid_metadata, - layer_definition.inspect_restored, + layer_definition.invalid_metadata_action, + layer_definition.restored_layer_action, layer_name.borrow(), &self.layers_dir, ) @@ -390,7 +390,7 @@ impl BuildContext { cache: false, }, &|_| InvalidMetadataAction::DeleteLayer, - &|_: &GenericMetadata, _| InspectRestoredAction::DeleteLayer, + &|_: &GenericMetadata, _| RestoredLayerAction::DeleteLayer, layer_name.borrow(), &self.layers_dir, ) diff --git a/libcnb/src/layer/struct_api/handling.rs b/libcnb/src/layer/struct_api/handling.rs index 3293e024..f023e614 100644 --- a/libcnb/src/layer/struct_api/handling.rs +++ b/libcnb/src/layer/struct_api/handling.rs @@ -3,8 +3,8 @@ use crate::layer::shared::{ WriteLayerError, }; use crate::layer::{ - EmptyLayerCause, InspectRestoredAction, IntoAction, InvalidMetadataAction, LayerError, - LayerRef, LayerState, + EmptyLayerCause, IntoAction, InvalidMetadataAction, LayerError, LayerRef, LayerState, + RestoredLayerAction, }; use crate::Buildpack; use libcnb_common::toml_file::read_toml_file; @@ -16,18 +16,18 @@ use serde::Serialize; use std::marker::PhantomData; use std::path::{Path, PathBuf}; -pub(crate) fn handle_layer( +pub(crate) fn handle_layer( layer_types: LayerTypes, - invalid_metadata: &dyn Fn(&GenericMetadata) -> MA, - inspect_restored: &dyn Fn(&M, &Path) -> IA, + invalid_metadata_action_fn: &dyn Fn(&GenericMetadata) -> MA, + restored_layer_action_fn: &dyn Fn(&M, &Path) -> RA, layer_name: &LayerName, layers_dir: &Path, -) -> crate::Result, B::Error> +) -> crate::Result, B::Error> where B: Buildpack + ?Sized, M: Serialize + DeserializeOwned, MA: IntoAction, MAC, B::Error>, - IA: IntoAction, + RA: IntoAction, { match read_layer::(layers_dir, layer_name) { Ok(None) => create_layer( @@ -37,12 +37,13 @@ where EmptyLayerCause::Uncached, ), Ok(Some(layer_data)) => { - let inspect_action = inspect_restored(&layer_data.metadata.metadata, &layer_data.path) - .into_action() - .map_err(crate::Error::BuildpackError)?; + let inspect_action = + restored_layer_action_fn(&layer_data.metadata.metadata, &layer_data.path) + .into_action() + .map_err(crate::Error::BuildpackError)?; match inspect_action { - (InspectRestoredAction::DeleteLayer, cause) => { + (RestoredLayerAction::DeleteLayer, cause) => { delete_layer(layers_dir, layer_name).map_err(LayerError::DeleteLayerError)?; create_layer( @@ -52,7 +53,7 @@ where EmptyLayerCause::Inspect { cause }, ) } - (InspectRestoredAction::KeepLayer, cause) => { + (RestoredLayerAction::KeepLayer, cause) => { // Always write the layer types as: // a) they might be different from what is currently on disk // b) the cache field will be removed by CNB lifecycle on cache restore @@ -75,9 +76,10 @@ where ) .map_err(LayerError::CouldNotReadGenericLayerMetadata)?; - let invalid_metadata_action = invalid_metadata(&layer_content_metadata.metadata) - .into_action() - .map_err(crate::Error::BuildpackError)?; + let invalid_metadata_action = + invalid_metadata_action_fn(&layer_content_metadata.metadata) + .into_action() + .map_err(crate::Error::BuildpackError)?; match invalid_metadata_action { (InvalidMetadataAction::DeleteLayer, cause) => { @@ -97,8 +99,8 @@ where handle_layer( layer_types, - invalid_metadata, - inspect_restored, + invalid_metadata_action_fn, + restored_layer_action_fn, layer_name, layers_dir, ) @@ -109,12 +111,12 @@ where } } -fn create_layer( +fn create_layer( layer_types: LayerTypes, layer_name: &LayerName, layers_dir: &Path, - empty_layer_cause: EmptyLayerCause, -) -> Result, crate::Error> + empty_layer_cause: EmptyLayerCause, +) -> Result, crate::Error> where B: Buildpack + ?Sized, { @@ -148,7 +150,7 @@ mod tests { use crate::build::{BuildContext, BuildResult}; use crate::detect::{DetectContext, DetectResult}; use crate::generic::{GenericError, GenericPlatform}; - use crate::layer::{EmptyLayerCause, InspectRestoredAction, InvalidMetadataAction, LayerState}; + use crate::layer::{EmptyLayerCause, InvalidMetadataAction, LayerState, RestoredLayerAction}; use crate::Buildpack; use libcnb_common::toml_file::read_toml_file; use libcnb_data::generic::GenericMetadata; @@ -204,7 +206,7 @@ mod tests { TestBuildpack, GenericMetadata, InvalidMetadataAction, - InspectRestoredAction, + RestoredLayerAction, (), (), >( @@ -213,8 +215,8 @@ mod tests { launch: true, cache: true, }, - &|_| panic!("invalid_metadata callback should not be called!"), - &|_, _| panic!("inspect_restored callback should not be called!"), + &|_| panic!("invalid_metadata_action callback should not be called!"), + &|_, _| panic!("restored_layer_action callback should not be called!"), &layer_name, temp_dir.path(), ) @@ -267,11 +269,11 @@ mod tests { launch: true, cache: true, }, - &|_| panic!("invalid_metadata callback should not be called!"), + &|_| panic!("invalid_metadata_action callback should not be called!"), &|metadata, path| { assert_eq!(metadata, &Some(toml! { answer = 42 })); assert_eq!(path, temp_dir.path().join(&*layer_name.clone())); - (InspectRestoredAction::KeepLayer, KEEP_CAUSE) + (RestoredLayerAction::KeepLayer, KEEP_CAUSE) }, &layer_name, temp_dir.path(), @@ -320,11 +322,11 @@ mod tests { launch: true, cache: true, }, - &|_| panic!("invalid_metadata callback should not be called!"), + &|_| panic!("invalid_metadata_action callback should not be called!"), &|metadata, path| { assert_eq!(metadata, &Some(toml! { answer = 42 })); assert_eq!(path, temp_dir.path().join(&*layer_name.clone())); - (InspectRestoredAction::DeleteLayer, DELETE_CAUSE) + (RestoredLayerAction::DeleteLayer, DELETE_CAUSE) }, &layer_name, temp_dir.path(), @@ -382,7 +384,7 @@ mod tests { TestBuildpack, TestLayerMetadata, _, - (InspectRestoredAction, &str), + (RestoredLayerAction, &str), &str, _, >( @@ -395,7 +397,7 @@ mod tests { assert_eq!(metadata, &Some(toml! { answer = 42 })); (InvalidMetadataAction::DeleteLayer, DELETE_CAUSE) }, - &|_, _| panic!("inspect_restored callback should not be called!"), + &|_, _| panic!("restored_layer_action callback should not be called!"), &layer_name, temp_dir.path(), ) @@ -474,7 +476,7 @@ mod tests { } ); - (InspectRestoredAction::KeepLayer, KEEP_CAUSE) + (RestoredLayerAction::KeepLayer, KEEP_CAUSE) }, &layer_name, temp_dir.path(), diff --git a/libcnb/src/layer/struct_api/mod.rs b/libcnb/src/layer/struct_api/mod.rs index 3e609954..7f171d51 100644 --- a/libcnb/src/layer/struct_api/mod.rs +++ b/libcnb/src/layer/struct_api/mod.rs @@ -19,7 +19,7 @@ use std::path::{Path, PathBuf}; /// A definition for a cached layer. /// /// Refer to the docs of [`BuildContext::cached_layer`] for usage examples. -pub struct CachedLayerDefinition<'a, M, MA, IA> { +pub struct CachedLayerDefinition<'a, M, MA, RA> { /// Whether the layer is intended for build. pub build: bool, /// Whether the layer is intended for launch. @@ -28,10 +28,10 @@ pub struct CachedLayerDefinition<'a, M, MA, IA> { /// /// Allows replacing the metadata before continuing (i.e. migration to a newer version) or /// deleting the layer. - pub invalid_metadata: &'a dyn Fn(&GenericMetadata) -> MA, + pub invalid_metadata_action: &'a dyn Fn(&GenericMetadata) -> MA, /// Callback when the layer was restored from cache to validate the contents and metadata. /// Can be used to delete existing cached layers. - pub inspect_restored: &'a dyn Fn(&M, &Path) -> IA, + pub restored_layer_action: &'a dyn Fn(&M, &Path) -> RA, } /// A definition for an uncached layer. @@ -56,7 +56,7 @@ pub enum InvalidMetadataAction { /// The action to take after inspecting restored layer data. #[derive(Copy, Clone, Debug)] -pub enum InspectRestoredAction { +pub enum RestoredLayerAction { /// Delete the restored layer. DeleteLayer, /// Keep the layer as-is. @@ -67,30 +67,30 @@ pub enum InspectRestoredAction { /// /// See: [`BuildContext::cached_layer`] and [`BuildContext::uncached_layer`] #[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub enum LayerState { +pub enum LayerState { /// The layer contains validated cached contents from a previous buildpack run. /// - /// See: `inspect_restored` in [`CachedLayerDefinition`]. - Restored { cause: IAC }, + /// See: `restored_layer_action` in [`CachedLayerDefinition`]. + Restored { cause: RAC }, /// The layer is empty. Inspect the contained [`EmptyLayerCause`] for the cause. - Empty { cause: EmptyLayerCause }, + Empty { cause: EmptyLayerCause }, } /// The cause of a layer being empty. #[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub enum EmptyLayerCause { +pub enum EmptyLayerCause { /// The layer wasn't cached in a previous buildpack run and was freshly created. Uncached, /// The layer was cached in a previous buildpack run, but the metadata was invalid and couldn't /// be converted into a valid form. Subsequently, the layer was deleted entirely. /// - /// See: `invalid_metadata` in [`CachedLayerDefinition`]. + /// See: `invalid_metadata_action` in [`CachedLayerDefinition`]. MetadataInvalid { cause: MAC }, - /// The layer was cached in a previous buildpack run, but the `inspect_restored` function + /// The layer was cached in a previous buildpack run, but the `restored_layer_action` function /// rejected the contents and/or metadata. /// - /// See: `inspect_restored` in [`CachedLayerDefinition`]. - Inspect { cause: IAC }, + /// See: `restored_layer_action` in [`CachedLayerDefinition`]. + Inspect { cause: RAC }, } /// A value-to-value conversion for layer actions. @@ -101,10 +101,10 @@ pub enum EmptyLayerCause { /// Implement this trait if you want to use your own types as actions. /// /// libcnb ships with generic implementations for the majority of the use-cases: -/// - Using [`InspectRestoredAction`] or [`InvalidMetadataAction`] directly. -/// - Using [`InspectRestoredAction`] or [`InvalidMetadataAction`] directly, wrapped in a Result. -/// - Using [`InspectRestoredAction`] or [`InvalidMetadataAction`] with a cause value in a tuple. -/// - Using [`InspectRestoredAction`] or [`InvalidMetadataAction`] with a cause value in a tuple, wrapped in a Result. +/// - Using [`RestoredLayerAction`] or [`InvalidMetadataAction`] directly. +/// - Using [`RestoredLayerAction`] or [`InvalidMetadataAction`] directly, wrapped in a Result. +/// - Using [`RestoredLayerAction`] or [`InvalidMetadataAction`] with a cause value in a tuple. +/// - Using [`RestoredLayerAction`] or [`InvalidMetadataAction`] with a cause value in a tuple, wrapped in a Result. pub trait IntoAction { fn into_action(self) -> Result<(T, C), E>; } @@ -143,7 +143,7 @@ impl IntoAction for Result<(T, C), E> { /// exec.d programs. /// /// To obtain a such a reference, use [`BuildContext::cached_layer`] or [`BuildContext::uncached_layer`]. -pub struct LayerRef +pub struct LayerRef where B: Buildpack + ?Sized, { @@ -154,10 +154,10 @@ where // the layers_dir here. layers_dir: PathBuf, buildpack: PhantomData, - pub state: LayerState, + pub state: LayerState, } -impl LayerRef +impl LayerRef where B: Buildpack, { From e317deb45145391875aa5952b3ded8ec68c36859 Mon Sep 17 00:00:00 2001 From: Manuel Fuchs Date: Tue, 18 Jun 2024 10:02:21 +0200 Subject: [PATCH 22/29] RustDoc update --- libcnb/src/build.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libcnb/src/build.rs b/libcnb/src/build.rs index e61b2864..8729d45f 100644 --- a/libcnb/src/build.rs +++ b/libcnb/src/build.rs @@ -127,7 +127,7 @@ impl BuildContext { /// invalidation. /// /// Users of this function pass in a [`CachedLayerDefinition`] that describes the desired layer - /// and the returned `LayerRef` can then be used to modify the layer like any other path. This + /// and the returned [`LayerRef`] can then be used to modify the layer like any other path. This /// allows users to be flexible in how and when the layer is modified and to abstract layer /// creation away if necessary. /// From 603f0c500e97002376f40fefc5f61fa674a984a4 Mon Sep 17 00:00:00 2001 From: Manuel Fuchs Date: Tue, 18 Jun 2024 10:39:23 +0200 Subject: [PATCH 23/29] Renamed EmptyLayerCause variants --- libcnb/src/build.rs | 4 ++-- libcnb/src/layer/struct_api/handling.rs | 14 +++++++------- libcnb/src/layer/struct_api/mod.rs | 8 ++++---- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/libcnb/src/build.rs b/libcnb/src/build.rs index 8729d45f..b7db1fbd 100644 --- a/libcnb/src/build.rs +++ b/libcnb/src/build.rs @@ -313,10 +313,10 @@ impl BuildContext { /// // Since the cause is just a regular Rust value, we can match it with regular /// // Rust syntax and be as complex or simple as we need. /// let message = match cause { - /// EmptyLayerCause::Inspect { + /// EmptyLayerCause::RestoredLayerAction { /// cause: CustomCause::LegacyVersion, /// } => "Re-installing language runtime (legacy cached version)", - /// EmptyLayerCause::Inspect { + /// EmptyLayerCause::RestoredLayerAction { /// cause: CustomCause::HasBrokenModule | CustomCause::MissingModulesFile, /// } => "Re-installing language runtime (broken modules detected)", /// _ => "Installing language runtime", diff --git a/libcnb/src/layer/struct_api/handling.rs b/libcnb/src/layer/struct_api/handling.rs index f023e614..a23cc518 100644 --- a/libcnb/src/layer/struct_api/handling.rs +++ b/libcnb/src/layer/struct_api/handling.rs @@ -34,7 +34,7 @@ where layer_types, layer_name, layers_dir, - EmptyLayerCause::Uncached, + EmptyLayerCause::NewlyCreated, ), Ok(Some(layer_data)) => { let inspect_action = @@ -50,7 +50,7 @@ where layer_types, layer_name, layers_dir, - EmptyLayerCause::Inspect { cause }, + EmptyLayerCause::RestoredLayerAction { cause }, ) } (RestoredLayerAction::KeepLayer, cause) => { @@ -89,7 +89,7 @@ where layer_types, layer_name, layers_dir, - EmptyLayerCause::MetadataInvalid { cause }, + EmptyLayerCause::InvalidMetadataAction { cause }, ) } (InvalidMetadataAction::ReplaceMetadata(metadata), _) => { @@ -164,7 +164,7 @@ mod tests { fn create_layer() { let temp_dir = tempdir().unwrap(); - let cause = EmptyLayerCause::Inspect { cause: () }; + let cause = EmptyLayerCause::RestoredLayerAction { cause: () }; let layer_name = layer_name!("test_layer"); let layer_ref = super::create_layer::( LayerTypes { @@ -241,7 +241,7 @@ mod tests { assert_eq!( layer_ref.state, LayerState::Empty { - cause: EmptyLayerCause::Uncached + cause: EmptyLayerCause::NewlyCreated } ); } @@ -352,7 +352,7 @@ mod tests { assert_eq!( layer_ref.state, LayerState::Empty { - cause: EmptyLayerCause::Inspect { + cause: EmptyLayerCause::RestoredLayerAction { cause: DELETE_CAUSE } } @@ -422,7 +422,7 @@ mod tests { assert_eq!( layer_ref.state, LayerState::Empty { - cause: EmptyLayerCause::MetadataInvalid { + cause: EmptyLayerCause::InvalidMetadataAction { cause: DELETE_CAUSE } } diff --git a/libcnb/src/layer/struct_api/mod.rs b/libcnb/src/layer/struct_api/mod.rs index 7f171d51..2125523d 100644 --- a/libcnb/src/layer/struct_api/mod.rs +++ b/libcnb/src/layer/struct_api/mod.rs @@ -79,18 +79,18 @@ pub enum LayerState { /// The cause of a layer being empty. #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum EmptyLayerCause { - /// The layer wasn't cached in a previous buildpack run and was freshly created. - Uncached, + /// The layer wasn't cached in a previous buildpack run and was newly created. + NewlyCreated, /// The layer was cached in a previous buildpack run, but the metadata was invalid and couldn't /// be converted into a valid form. Subsequently, the layer was deleted entirely. /// /// See: `invalid_metadata_action` in [`CachedLayerDefinition`]. - MetadataInvalid { cause: MAC }, + InvalidMetadataAction { cause: MAC }, /// The layer was cached in a previous buildpack run, but the `restored_layer_action` function /// rejected the contents and/or metadata. /// /// See: `restored_layer_action` in [`CachedLayerDefinition`]. - Inspect { cause: RAC }, + RestoredLayerAction { cause: RAC }, } /// A value-to-value conversion for layer actions. From 3f2f832ee1fc2b018f478ef3fc3c9d921d267ced Mon Sep 17 00:00:00 2001 From: Manuel Fuchs Date: Tue, 18 Jun 2024 10:44:13 +0200 Subject: [PATCH 24/29] Explain IntoAction in cached_layer example --- libcnb/src/build.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/libcnb/src/build.rs b/libcnb/src/build.rs index b7db1fbd..75d8ec12 100644 --- a/libcnb/src/build.rs +++ b/libcnb/src/build.rs @@ -274,6 +274,11 @@ impl BuildContext { /// invalid_metadata_action: &|_| InvalidMetadataAction::DeleteLayer, /// restored_layer_action: &|metadata: &ExampleLayerMetadata, layer_dir| { /// if metadata.lang_runtime_version.starts_with("0.") { + /// // The return value for restored_layer_action can be anything with an + /// // IntoAction implementation. libcnb provides built-in implementations + /// // for raw RestoredLayerAction/InvalidMetadataAction values, tuples of + /// // actions with a cause value (of any type) plus variants that are wrapped + /// // in a Result. See IntoAction for details. /// Ok(( /// RestoredLayerAction::DeleteLayer, /// CustomCause::LegacyVersion, From 400b1b334181e5d2036a4aa13a586a464ce38ae9 Mon Sep 17 00:00:00 2001 From: Manuel Fuchs Date: Tue, 18 Jun 2024 10:45:58 +0200 Subject: [PATCH 25/29] Explain IntoAction cached_layer docs --- libcnb/src/build.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libcnb/src/build.rs b/libcnb/src/build.rs index 75d8ec12..e4c6d952 100644 --- a/libcnb/src/build.rs +++ b/libcnb/src/build.rs @@ -131,6 +131,9 @@ impl BuildContext { /// allows users to be flexible in how and when the layer is modified and to abstract layer /// creation away if necessary. /// + /// See [`IntoAction`] for details which values can be returned from the + /// `invalid_metadata_action` and `restored_layer_action` functions. + /// /// # Basic Example /// ```rust /// # use libcnb::build::{BuildContext, BuildResult, BuildResultBuilder}; From a4877e4465c0a735d1038a807d337e8f7063f3cb Mon Sep 17 00:00:00 2001 From: Manuel Fuchs Date: Tue, 18 Jun 2024 11:04:26 +0200 Subject: [PATCH 26/29] Add uncached_layer example --- libcnb/src/build.rs | 68 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/libcnb/src/build.rs b/libcnb/src/build.rs index e4c6d952..468ecde9 100644 --- a/libcnb/src/build.rs +++ b/libcnb/src/build.rs @@ -384,6 +384,74 @@ impl BuildContext { /// data will be deleted. /// /// This function is essentially the same as [`BuildContext::uncached_layer`] but simpler. + /// + /// # Example + /// ```rust + /// # use libcnb::build::{BuildContext, BuildResult, BuildResultBuilder}; + /// # use libcnb::detect::{DetectContext, DetectResult}; + /// # use libcnb::generic::GenericPlatform; + /// # use libcnb::layer::{ + /// # UncachedLayerDefinition, RestoredLayerAction, InvalidMetadataAction, LayerState, + /// # }; + /// # use libcnb::layer_env::{LayerEnv, ModificationBehavior, Scope}; + /// # use libcnb::Buildpack; + /// # use libcnb_data::generic::GenericMetadata; + /// # use libcnb_data::layer_name; + /// # use std::fs; + /// # + /// # struct ExampleBuildpack; + /// # + /// # #[derive(Debug)] + /// # enum ExampleBuildpackError { + /// # WriteDataError(std::io::Error), + /// # } + /// # + /// # impl Buildpack for ExampleBuildpack { + /// # type Platform = GenericPlatform; + /// # type Metadata = GenericMetadata; + /// # type Error = ExampleBuildpackError; + /// # + /// # fn detect(&self, context: DetectContext) -> libcnb::Result { + /// # unimplemented!() + /// # } + /// # + /// # fn build(&self, context: BuildContext) -> libcnb::Result { + /// let layer_ref = context.uncached_layer( + /// layer_name!("example_layer"), + /// UncachedLayerDefinition { + /// build: false, + /// launch: false, + /// }, + /// )?; + /// + /// println!("Creating new example layer!"); + /// + /// // Modify the layer contents with regular Rust functions: + /// fs::write( + /// layer_ref.path().join("incantation.txt"), + /// "Phol ende uuodan uuorun zi holza. Du uuart demo balderes uolon sin uuoz birenkit.", + /// ) + /// .map_err(ExampleBuildpackError::WriteDataError)?; + /// + /// // Use functions on LayerRef for common CNB specific layer modifications: + /// layer_ref.write_env(LayerEnv::new().chainable_insert( + /// Scope::All, + /// ModificationBehavior::Append, + /// "PLANET", + /// "LV-246", + /// ))?; + /// + /// # + /// # BuildResultBuilder::new().build() + /// # } + /// # } + /// # + /// # impl From for libcnb::Error { + /// # fn from(value: ExampleBuildpackError) -> Self { + /// # Self::BuildpackError(value) + /// # } + /// # } + /// ``` pub fn uncached_layer( &self, layer_name: impl Borrow, From 2625478e0331e269bb7bd38cdb70d2421d4b7f40 Mon Sep 17 00:00:00 2001 From: Manuel Fuchs Date: Tue, 18 Jun 2024 11:06:24 +0200 Subject: [PATCH 27/29] Rename remaining inspect_action related names --- libcnb/src/layer/struct_api/handling.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libcnb/src/layer/struct_api/handling.rs b/libcnb/src/layer/struct_api/handling.rs index a23cc518..6f02da10 100644 --- a/libcnb/src/layer/struct_api/handling.rs +++ b/libcnb/src/layer/struct_api/handling.rs @@ -37,12 +37,12 @@ where EmptyLayerCause::NewlyCreated, ), Ok(Some(layer_data)) => { - let inspect_action = + let restored_layer_action = restored_layer_action_fn(&layer_data.metadata.metadata, &layer_data.path) .into_action() .map_err(crate::Error::BuildpackError)?; - match inspect_action { + match restored_layer_action { (RestoredLayerAction::DeleteLayer, cause) => { delete_layer(layers_dir, layer_name).map_err(LayerError::DeleteLayerError)?; From 11b6ce1aa11caf95a83aa364583162042d664648 Mon Sep 17 00:00:00 2001 From: Manuel Fuchs Date: Tue, 18 Jun 2024 11:10:33 +0200 Subject: [PATCH 28/29] Add LayerRef::read_env --- libcnb/src/layer/struct_api/mod.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/libcnb/src/layer/struct_api/mod.rs b/libcnb/src/layer/struct_api/mod.rs index 2125523d..0ec75d1c 100644 --- a/libcnb/src/layer/struct_api/mod.rs +++ b/libcnb/src/layer/struct_api/mod.rs @@ -4,7 +4,7 @@ pub(crate) mod handling; #[allow(unused)] use crate::build::BuildContext; use crate::layer::shared::{replace_layer_exec_d_programs, replace_layer_sboms, WriteLayerError}; -use crate::layer::LayerError; +use crate::layer::{LayerError, ReadLayerError}; use crate::layer_env::LayerEnv; use crate::sbom::Sbom; use crate::Buildpack; @@ -195,6 +195,13 @@ where }) } + /// Reads the current layer environment from disk. + pub fn read_env(&self) -> crate::Result { + LayerEnv::read_from_layer_dir(self.path()).map_err(|error| { + crate::Error::LayerError(LayerError::ReadLayerError(ReadLayerError::IoError(error))) + }) + } + /// Writes the given SBOMs to disk. /// /// Any existing SBOMs will be overwritten. From 57683ade08afc85401d9c007bc52cfdf43ed6501 Mon Sep 17 00:00:00 2001 From: Manuel Fuchs Date: Tue, 18 Jun 2024 12:57:05 +0200 Subject: [PATCH 29/29] RustDoc improvements --- libcnb/src/build.rs | 4 ++-- libcnb/src/layer/struct_api/mod.rs | 7 +++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/libcnb/src/build.rs b/libcnb/src/build.rs index 468ecde9..234a3149 100644 --- a/libcnb/src/build.rs +++ b/libcnb/src/build.rs @@ -131,7 +131,7 @@ impl BuildContext { /// allows users to be flexible in how and when the layer is modified and to abstract layer /// creation away if necessary. /// - /// See [`IntoAction`] for details which values can be returned from the + /// See [`IntoAction`] for details on which values can be returned from the /// `invalid_metadata_action` and `restored_layer_action` functions. /// /// # Basic Example @@ -440,7 +440,7 @@ impl BuildContext { /// "PLANET", /// "LV-246", /// ))?; - /// + /// # /// # /// # BuildResultBuilder::new().build() /// # } diff --git a/libcnb/src/layer/struct_api/mod.rs b/libcnb/src/layer/struct_api/mod.rs index 0ec75d1c..ecc8969c 100644 --- a/libcnb/src/layer/struct_api/mod.rs +++ b/libcnb/src/layer/struct_api/mod.rs @@ -54,12 +54,12 @@ pub enum InvalidMetadataAction { ReplaceMetadata(M), } -/// The action to take after inspecting restored layer data. +/// The action to take when a previously cached layer was restored. #[derive(Copy, Clone, Debug)] pub enum RestoredLayerAction { /// Delete the restored layer. DeleteLayer, - /// Keep the layer as-is. + /// Keep the restored layer. It can then be used as-is or updated if required. KeepLayer, } @@ -196,6 +196,9 @@ where } /// Reads the current layer environment from disk. + /// + /// Note that this includes implicit entries such as adding `bin/` to `PATH`. See [`LayerEnv`] + /// docs for details on implicit entries. pub fn read_env(&self) -> crate::Result { LayerEnv::read_from_layer_dir(self.path()).map_err(|error| { crate::Error::LayerError(LayerError::ReadLayerError(ReadLayerError::IoError(error)))