-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Migrate
go_deps
to struct layer API (#339)
* Migrate go_deps layer to struct layer API * Drop go_clean, which is no longer used * Drop infallible result and logging side effects
- Loading branch information
1 parent
747a19e
commit 65eb616
Showing
3 changed files
with
60 additions
and
102 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,108 +1,89 @@ | ||
use crate::{cmd, GoBuildpack, GoBuildpackError}; | ||
use crate::{GoBuildpack, GoBuildpackError}; | ||
use libcnb::build::BuildContext; | ||
use libcnb::data::layer_content_metadata::LayerTypes; | ||
use libcnb::layer::{ExistingLayerStrategy, Layer, LayerData, LayerResult, LayerResultBuilder}; | ||
use libcnb::data::layer_name; | ||
use libcnb::layer::{ | ||
CachedLayerDefinition, EmptyLayerCause, InvalidMetadataAction, LayerState, RestoredLayerAction, | ||
}; | ||
use libcnb::layer_env::{LayerEnv, Scope}; | ||
use libcnb::{Buildpack, Env}; | ||
use libherokubuildpack::log::log_info; | ||
use serde::{Deserialize, Serialize}; | ||
use std::fs; | ||
use std::path::Path; | ||
|
||
const LAYER_VERSION: &str = "1"; | ||
const MAX_CACHE_USAGE_COUNT: f32 = 100.0; | ||
const CACHE_ENV: &str = "GOMODCACHE"; | ||
const CACHE_DIR: &str = "cache"; | ||
|
||
/// A layer that caches the go modules cache | ||
pub(crate) struct DepsLayer { | ||
pub(crate) go_env: Env, | ||
} | ||
|
||
#[derive(Deserialize, Serialize, Clone, PartialEq)] | ||
pub(crate) struct DepsLayerMetadata { | ||
// Using float here due to [an issue with lifecycle's handling of integers](https://github.com/buildpacks/lifecycle/issues/884) | ||
cache_usage_count: f32, | ||
layer_version: String, | ||
} | ||
|
||
#[derive(thiserror::Error, Debug)] | ||
pub(crate) enum DepsLayerError { | ||
#[error("Couldn't create Go modules cache layer: {0}")] | ||
Create(std::io::Error), | ||
#[error("Couldn't clean Go modules cache: {0}")] | ||
Clean(#[from] cmd::Error), | ||
} | ||
|
||
impl Layer for DepsLayer { | ||
type Buildpack = GoBuildpack; | ||
type Metadata = DepsLayerMetadata; | ||
|
||
fn types(&self) -> LayerTypes { | ||
LayerTypes { | ||
/// Create or restore the layer for the go modules cache (non-vendored dependencies) | ||
pub(crate) fn handle_deps_layer( | ||
context: &BuildContext<GoBuildpack>, | ||
) -> libcnb::Result<LayerEnv, GoBuildpackError> { | ||
let layer_ref = context.cached_layer( | ||
layer_name!("go_deps"), | ||
CachedLayerDefinition { | ||
build: true, | ||
launch: false, | ||
cache: true, | ||
} | ||
} | ||
invalid_metadata_action: &|_| InvalidMetadataAction::DeleteLayer, | ||
restored_layer_action: &|restored_metadata: &DepsLayerMetadata, _| { | ||
if restored_metadata.cache_usage_count >= MAX_CACHE_USAGE_COUNT { | ||
return ( | ||
RestoredLayerAction::DeleteLayer, | ||
restored_metadata.cache_usage_count, | ||
); | ||
} | ||
( | ||
RestoredLayerAction::KeepLayer, | ||
restored_metadata.cache_usage_count, | ||
) | ||
}, | ||
}, | ||
)?; | ||
|
||
fn create( | ||
&mut self, | ||
_ctx: &BuildContext<Self::Buildpack>, | ||
layer_path: &Path, | ||
) -> Result<LayerResult<Self::Metadata>, GoBuildpackError> { | ||
log_info("Creating new Go modules cache"); | ||
let cache_dir = layer_path.join(CACHE_DIR); | ||
fs::create_dir(&cache_dir).map_err(DepsLayerError::Create)?; | ||
LayerResultBuilder::new(DepsLayerMetadata { | ||
cache_usage_count: 1.0, | ||
layer_version: LAYER_VERSION.to_string(), | ||
}) | ||
.env(LayerEnv::new().chainable_insert( | ||
Scope::Build, | ||
libcnb::layer_env::ModificationBehavior::Override, | ||
CACHE_ENV, | ||
cache_dir, | ||
)) | ||
.build() | ||
match layer_ref.state { | ||
LayerState::Empty { | ||
cause: EmptyLayerCause::NewlyCreated, | ||
} => (), | ||
LayerState::Empty { | ||
cause: EmptyLayerCause::RestoredLayerAction { .. }, | ||
} => log_info("Discarding expired Go modules cache"), | ||
LayerState::Empty { .. } => log_info("Discarding invalid Go modules cache"), | ||
LayerState::Restored { .. } => log_info("Reusing Go modules cache"), | ||
} | ||
|
||
fn update( | ||
&mut self, | ||
_ctx: &BuildContext<Self::Buildpack>, | ||
layer: &LayerData<Self::Metadata>, | ||
) -> Result<LayerResult<Self::Metadata>, GoBuildpackError> { | ||
LayerResultBuilder::new(DepsLayerMetadata { | ||
cache_usage_count: layer.content_metadata.metadata.cache_usage_count + 1.0, | ||
layer_version: LAYER_VERSION.to_string(), | ||
}) | ||
.env(LayerEnv::new().chainable_insert( | ||
Scope::Build, | ||
libcnb::layer_env::ModificationBehavior::Override, | ||
CACHE_ENV, | ||
layer.path.join(CACHE_DIR), | ||
)) | ||
.build() | ||
let mut cache_usage_count = 1.0; | ||
match layer_ref.state { | ||
LayerState::Restored { | ||
cause: previous_cache_usage_count, | ||
} => cache_usage_count += previous_cache_usage_count, | ||
LayerState::Empty { .. } => { | ||
log_info("Creating new Go modules cache"); | ||
let cache_dir = layer_ref.path().join(CACHE_DIR); | ||
fs::create_dir(&cache_dir).map_err(DepsLayerError::Create)?; | ||
layer_ref.write_env(LayerEnv::new().chainable_insert( | ||
Scope::Build, | ||
libcnb::layer_env::ModificationBehavior::Override, | ||
CACHE_ENV, | ||
cache_dir, | ||
))?; | ||
} | ||
} | ||
layer_ref.write_metadata(DepsLayerMetadata { cache_usage_count })?; | ||
layer_ref.read_env() | ||
} | ||
|
||
fn existing_layer_strategy( | ||
&mut self, | ||
_ctx: &BuildContext<Self::Buildpack>, | ||
layer: &LayerData<Self::Metadata>, | ||
) -> Result<ExistingLayerStrategy, <Self::Buildpack as Buildpack>::Error> { | ||
if layer.content_metadata.metadata.cache_usage_count >= MAX_CACHE_USAGE_COUNT | ||
|| layer.content_metadata.metadata.layer_version != LAYER_VERSION | ||
{ | ||
log_info("Expired Go modules cache"); | ||
// Go restricts write permissions in cache folders, which blocks libcnb | ||
// from deleting the layer in a `Recreate` scenario. Go clean will | ||
// delete the entire cache, including the restricted access files. | ||
let mut go_env = self.go_env.clone(); | ||
go_env.insert(CACHE_ENV, layer.path.join(CACHE_DIR)); | ||
cmd::go_clean("-modcache", &go_env).map_err(DepsLayerError::Clean)?; | ||
return Ok(ExistingLayerStrategy::Recreate); | ||
} | ||
log_info("Reusing Go modules cache"); | ||
Ok(ExistingLayerStrategy::Update) | ||
impl From<DepsLayerError> for libcnb::Error<GoBuildpackError> { | ||
fn from(value: DepsLayerError) -> Self { | ||
libcnb::Error::BuildpackError(GoBuildpackError::DepsLayer(value)) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters