Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Shared layer logic for consistent output and unit testing #327

Merged
merged 19 commits into from
Oct 2, 2024
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions buildpacks/ruby/src/layers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ pub(crate) mod bundle_download_layer;
pub(crate) mod bundle_install_layer;
pub(crate) mod metrics_agent_install;
pub(crate) mod ruby_install_layer;
mod shared;
182 changes: 88 additions & 94 deletions buildpacks/ruby/src/layers/ruby_install_layer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,19 @@
//!
//! When the Ruby version changes, invalidate and re-run.
//!
use bullet_stream::state::SubBullet;
use bullet_stream::{style, Print};
use commons::display::SentenceList;
use libcnb::data::layer_name;
use libcnb::layer::{
CachedLayerDefinition, EmptyLayerCause, InvalidMetadataAction, LayerState, RestoredLayerAction,
};
use libcnb::layer_env::LayerEnv;
use magic_migrate::{try_migrate_deserializer_chain, TryMigrate};

use crate::layers::shared::{cached_layer_write_metadata, MetadataDiff};
use crate::{
target_id::{TargetId, TargetIdError},
RubyBuildpack, RubyBuildpackError,
};
use bullet_stream::state::SubBullet;
use bullet_stream::{style, Print};
use commons::gemfile_lock::ResolvedRubyVersion;
use flate2::read::GzDecoder;
use libcnb::data::layer_name;
use libcnb::layer::{EmptyLayerCause, LayerState};
use libcnb::layer_env::LayerEnv;
use magic_migrate::{try_migrate_deserializer_chain, TryMigrate};
use serde::{Deserialize, Deserializer, Serialize};
use std::convert::Infallible;
use std::io::{self, Stdout};
Expand All @@ -38,67 +35,26 @@ use url::Url;
pub(crate) fn handle(
context: &libcnb::build::BuildContext<RubyBuildpack>,
mut bullet: Print<SubBullet<Stdout>>,
metadata: Metadata,
metadata: &Metadata,
) -> libcnb::Result<(Print<SubBullet<Stdout>>, LayerEnv), RubyBuildpackError> {
let layer_ref = context.cached_layer(
layer_name!("ruby"),
CachedLayerDefinition {
build: true,
launch: true,
invalid_metadata_action: &|old| match Metadata::try_from_str_migrations(
&toml::to_string(old).expect("TOML deserialization of GenericMetadata"),
) {
Some(Ok(migrated)) => (
InvalidMetadataAction::ReplaceMetadata(migrated),
"replaced metadata".to_string(),
),
Some(Err(error)) => (
InvalidMetadataAction::DeleteLayer,
format!("metadata migration error {error}"),
),
None => (
InvalidMetadataAction::DeleteLayer,
"invalid metadata".to_string(),
),
},
restored_layer_action: &|old: &Metadata, _| {
let diff = metadata_diff(old, &metadata);
if diff.is_empty() {
(
RestoredLayerAction::KeepLayer,
"using cached version".to_string(),
)
} else {
(
RestoredLayerAction::DeleteLayer,
format!(
"due to {changes}: {differences}",
changes = if diff.len() > 1 { "changes" } else { "change" },
differences = SentenceList::new(&diff)
),
)
}
},
},
)?;
let layer_ref = cached_layer_write_metadata(layer_name!("ruby"), context, metadata)?;
match &layer_ref.state {
LayerState::Restored { cause: _ } => {
bullet = bullet.sub_bullet("Using cached Ruby version");
LayerState::Restored { cause } => {
bullet = bullet.sub_bullet(cause);
}
LayerState::Empty { cause } => {
match cause {
EmptyLayerCause::NewlyCreated => {}
EmptyLayerCause::InvalidMetadataAction { cause }
| EmptyLayerCause::RestoredLayerAction { cause } => {
bullet = bullet.sub_bullet(format!("Clearing cache {cause}"));
bullet = bullet.sub_bullet(cause);
}
}
let timer = bullet.start_timer("Installing");
install_ruby(&metadata, &layer_ref.path())?;
install_ruby(metadata, &layer_ref.path())?;
bullet = timer.done();
}
}
layer_ref.write_metadata(metadata)?;
Ok((bullet, layer_ref.read_env()?))
}

Expand Down Expand Up @@ -170,44 +126,46 @@ impl TryFrom<MetadataV1> for MetadataV2 {
}
}

fn metadata_diff(old: &Metadata, metadata: &Metadata) -> Vec<String> {
let mut differences = Vec::new();
let Metadata {
distro_name,
distro_version,
cpu_architecture,
ruby_version,
} = old;
if ruby_version != &metadata.ruby_version {
differences.push(format!(
"Ruby version ({old} to {now})",
old = style::value(ruby_version.to_string()),
now = style::value(metadata.ruby_version.to_string())
));
}
if distro_name != &metadata.distro_name {
differences.push(format!(
"distro name ({old} to {now})",
old = style::value(distro_name),
now = style::value(&metadata.distro_name)
));
}
if distro_version != &metadata.distro_version {
differences.push(format!(
"distro version ({old} to {now})",
old = style::value(distro_version),
now = style::value(&metadata.distro_version)
));
}
if cpu_architecture != &metadata.cpu_architecture {
differences.push(format!(
"CPU architecture ({old} to {now})",
old = style::value(cpu_architecture),
now = style::value(&metadata.cpu_architecture)
));
}
impl MetadataDiff for Metadata {
fn diff(&self, old: &Self) -> Vec<String> {
let mut differences = Vec::new();
let Metadata {
distro_name,
distro_version,
cpu_architecture,
ruby_version,
} = old;
if ruby_version != &self.ruby_version {
differences.push(format!(
"Ruby version ({old} to {now})",
old = style::value(ruby_version.to_string()),
now = style::value(self.ruby_version.to_string())
));
}
if distro_name != &self.distro_name {
schneems marked this conversation as resolved.
Show resolved Hide resolved
differences.push(format!(
"distro name ({old} to {now})",
schneems marked this conversation as resolved.
Show resolved Hide resolved
old = style::value(distro_name),
now = style::value(&self.distro_name)
));
}
if distro_version != &self.distro_version {
differences.push(format!(
"distro version ({old} to {now})",
schneems marked this conversation as resolved.
Show resolved Hide resolved
old = style::value(distro_version),
now = style::value(&self.distro_version)
));
}
if cpu_architecture != &self.cpu_architecture {
differences.push(format!(
"CPU architecture ({old} to {now})",
old = style::value(cpu_architecture),
now = style::value(&self.cpu_architecture)
));
}

differences
differences
}
}

fn download_url(
Expand Down Expand Up @@ -291,6 +249,8 @@ pub(crate) enum RubyInstallError {

#[cfg(test)]
mod tests {
use crate::layers::shared::temp_build_context;

use super::*;

/// If this test fails due to a change you'll need to
Expand Down Expand Up @@ -362,4 +322,38 @@ version = "3.1.3"
"https://heroku-buildpack-ruby.s3.us-east-1.amazonaws.com/heroku-22/ruby-2.7.4.tgz",
);
}

#[test]
fn test_ruby_version_difference_clears_cache() {
let temp = tempfile::tempdir().unwrap();
let context = temp_build_context::<RubyBuildpack>(temp.path());
let old = Metadata {
ruby_version: ResolvedRubyVersion("2.7.2".to_string()),
distro_name: "ubuntu".to_string(),
distro_version: "20.04".to_string(),
cpu_architecture: "x86_64".to_string(),
};
let differences = old.diff(&old);
schneems marked this conversation as resolved.
Show resolved Hide resolved
assert_eq!(differences, Vec::<String>::new());

cached_layer_write_metadata(layer_name!("ruby"), &context, &old).unwrap();
let result = cached_layer_write_metadata(layer_name!("ruby"), &context, &old).unwrap();
let actual = result.state;
assert!(matches!(actual, LayerState::Restored { .. }));

let now = Metadata {
ruby_version: ResolvedRubyVersion("3.0.0".to_string()),
..old.clone()
};
let differences = now.diff(&old);
assert_eq!(differences.len(), 1);

let result = cached_layer_write_metadata(layer_name!("ruby"), &context, &now).unwrap();
assert!(matches!(
result.state,
LayerState::Empty {
cause: EmptyLayerCause::RestoredLayerAction { .. }
}
));
}
}
Loading