Skip to content

Commit

Permalink
Merge branch 'main' into disable_node_build_scripts
Browse files Browse the repository at this point in the history
  • Loading branch information
colincasey authored Oct 23, 2024
2 parents 597ae9c + 907c64d commit 9ad0264
Show file tree
Hide file tree
Showing 4 changed files with 211 additions and 16 deletions.
13 changes: 12 additions & 1 deletion buildpacks/static-web-server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,18 @@ Other buildpacks can return a [Build Plan](https://github.com/buildpacks/spec/bl

Configuration defined in an app's `project.toml` takes precedence over this inherited Build Plan configuration.

This example sets a doc root & index, but any [configuration](#configuration) options are supported:
This example sets `root` & `index` in the build plan, using supported [configuration](#configuration) options:

```toml
[[requires]]
name = "static-web-server"

[requires.metadata]
root = "wwwroot"
index = "index.htm"
```

Example using [libcnb.rs](https://github.com/heroku/libcnb.rs):

```rust
fn detect(&self, context: DetectContext<Self>) -> libcnb::Result<DetectResult, Self::Error> {
Expand Down
207 changes: 195 additions & 12 deletions buildpacks/static-web-server/src/config_web_server.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::caddy_config::CaddyConfig;
use crate::heroku_web_server_config::HerokuWebServerConfig;
use crate::{StaticWebServerBuildpack, StaticWebServerBuildpackError};
use crate::{StaticWebServerBuildpack, StaticWebServerBuildpackError, BUILD_PLAN_ID};
use libcnb::data::layer_name;
use libcnb::layer::LayerRef;
use libcnb::{build::BuildContext, layer::UncachedLayerDefinition};
Expand All @@ -22,15 +22,7 @@ pub(crate) fn config_web_server(
},
)?;

// Load a table of Build Plan [requires.metadata] from context.
// When a key is defined multiple times, the last one wins.
let mut build_plan_config = Table::new();
context.buildpack_plan.entries.iter().for_each(|e| {
e.metadata.iter().for_each(|(k, v)| {
build_plan_config.insert(k.to_owned(), v.to_owned());
});
});

let build_plan_config = generate_build_plan_config(context);
let project_config = read_project_config(context.app_dir.as_ref())
.map_err(StaticWebServerBuildpackError::CannotReadProjectToml)?;

Expand Down Expand Up @@ -72,6 +64,38 @@ pub(crate) fn config_web_server(
Ok(configuration_layer)
}

// Load a table of Build Plan [requires.metadata] from context.
// When a key is defined multiple times,
// * for tables: insert the new row value to the existing table
// * for other value types: the values overwrite, so the last one defined wins
fn generate_build_plan_config(
context: &BuildContext<StaticWebServerBuildpack>,
) -> toml::map::Map<String, toml::Value> {
let mut build_plan_config = Table::new();
context.buildpack_plan.entries.iter().for_each(|e| {
if e.name == BUILD_PLAN_ID {
e.metadata.iter().for_each(|(k, v)| {
if let Some(new_values) = v.as_table() {
if let Some(existing_values) =
build_plan_config.get(k).and_then(|ev| ev.as_table())
{
let mut all_values = existing_values.clone();
new_values.into_iter().for_each(|(nk, nv)| {
all_values.insert(nk.to_string(), nv.clone());
});
build_plan_config.insert(k.to_owned(), all_values.into());
} else {
build_plan_config.insert(k.to_owned(), v.to_owned());
}
} else {
build_plan_config.insert(k.to_owned(), v.to_owned());
}
});
}
});
build_plan_config
}

fn generate_config_with_inheritance(
project_config: Option<&toml::Value>,
config_to_inherit: &toml::map::Map<String, toml::Value>,
Expand Down Expand Up @@ -101,10 +125,131 @@ fn generate_config_with_inheritance(

#[cfg(test)]
mod tests {
use std::path::PathBuf;
use libcnb::{
build::BuildContext,
data::{
buildpack::{Buildpack, BuildpackApi, BuildpackVersion, ComponentBuildpackDescriptor},
buildpack_id,
buildpack_plan::{BuildpackPlan, Entry},
},
generic::GenericPlatform,
Env, Target,
};
use std::{collections::HashSet, path::PathBuf};
use toml::toml;

use crate::config_web_server::generate_config_with_inheritance;
use crate::{
config_web_server::{generate_build_plan_config, generate_config_with_inheritance},
StaticWebServerBuildpack, BUILD_PLAN_ID,
};

#[test]
fn generate_build_plan_config_from_one_entry() {
let test_build_plan = vec![Entry {
name: BUILD_PLAN_ID.to_string(),
metadata: toml! {
root = "testY"

[headers]
X-Server = "testX"
},
}];
let test_context = create_test_context(test_build_plan);
let result = generate_build_plan_config(&test_context);

let result_root = result
.get("root")
.expect("should contain the property: root");
assert_eq!(result_root, &toml::Value::String("testY".to_string()));

let result_headers = result.get("headers").expect("should contain headers");
let result_table = result_headers.as_table().expect("should contain atable");
assert_eq!(
result_table.get("X-Server"),
Some(&toml::Value::String("testX".to_string()))
);
}

#[test]
fn generate_build_plan_config_collects_headers_from_entries() {
let test_build_plan = vec![
Entry {
name: BUILD_PLAN_ID.to_string(),
metadata: toml! {
[headers]
X-Serve-1 = "test1"
},
},
Entry {
name: BUILD_PLAN_ID.to_string(),
metadata: toml! {
[headers]
X-Serve-2 = "test2"
X-Serve-3 = "test3"
},
},
Entry {
name: BUILD_PLAN_ID.to_string(),
metadata: toml! {
[headers]
X-Serve-4 = "test4"
},
},
];
let test_context = create_test_context(test_build_plan);
let result = generate_build_plan_config(&test_context);

let result_headers = result.get("headers").expect("should contain headers");
assert_eq!(
result_headers.get("X-Serve-1"),
Some(&toml::Value::String("test1".to_string()))
);
assert_eq!(
result_headers.get("X-Serve-2"),
Some(&toml::Value::String("test2".to_string()))
);
assert_eq!(
result_headers.get("X-Serve-3"),
Some(&toml::Value::String("test3".to_string()))
);
assert_eq!(
result_headers.get("X-Serve-4"),
Some(&toml::Value::String("test4".to_string()))
);
}

#[test]
fn generate_build_plan_config_captures_last_root_from_entries() {
let test_build_plan = vec![
Entry {
name: BUILD_PLAN_ID.to_string(),
metadata: toml! {
root = "test1"
},
},
Entry {
name: BUILD_PLAN_ID.to_string(),
metadata: toml! {
root = "test2"
},
},
];
let test_context = create_test_context(test_build_plan);
let result = generate_build_plan_config(&test_context);

let result_root = result
.get("root")
.expect("should contain the property: root");
assert_eq!(result_root, &toml::Value::String("test2".to_string()));
}

#[test]
fn generate_build_plan_config_empty() {
let test_build_plan = vec![];
let test_context = create_test_context(test_build_plan);
let result = generate_build_plan_config(&test_context);
assert!(result.is_empty());
}

#[test]
fn generate_config_default() {
Expand Down Expand Up @@ -168,4 +313,42 @@ mod tests {
assert_eq!(parsed_config.index, Some(String::from("main.html")));
assert_eq!(parsed_config.headers, None);
}

fn create_test_context(build_plan: Vec<Entry>) -> BuildContext<StaticWebServerBuildpack> {
let test_context: BuildContext<StaticWebServerBuildpack> = BuildContext {
layers_dir: PathBuf::new(),
app_dir: PathBuf::new(),
buildpack_dir: PathBuf::new(),
target: Target {
os: "test".to_string(),
arch: "test".to_string(),
arch_variant: None,
distro_name: "test".to_string(),
distro_version: "test".to_string(),
},
platform: GenericPlatform::new(<Env as std::default::Default>::default()),
buildpack_plan: BuildpackPlan {
entries: build_plan,
},
buildpack_descriptor: ComponentBuildpackDescriptor {
api: BuildpackApi { major: 0, minor: 0 },
buildpack: Buildpack {
id: buildpack_id!("heroku/test"),
name: None,
version: BuildpackVersion::new(0, 0, 0),
homepage: None,
clear_env: false,
description: None,
keywords: vec![],
licenses: vec![],
sbom_formats: HashSet::new(),
},
stacks: vec![],
targets: vec![],
metadata: None,
},
store: None,
};
test_context
}
}
5 changes: 3 additions & 2 deletions buildpacks/static-web-server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ use test_support as _;
use ureq as _;

const BUILDPACK_NAME: &str = "Heroku Static Web Server Buildpack";
const BUILD_PLAN_ID: &str = "static-web-server";
const WEB_SERVER_NAME: &str = "caddy";
const WEB_SERVER_VERSION: &str = "2.8.4";

Expand All @@ -37,8 +38,8 @@ impl Buildpack for StaticWebServerBuildpack {

fn detect(&self, _context: DetectContext<Self>) -> libcnb::Result<DetectResult, Self::Error> {
let plan_builder = BuildPlanBuilder::new()
.provides("static-web-server")
.requires(Require::new("static-web-server"));
.provides(BUILD_PLAN_ID)
.requires(Require::new(BUILD_PLAN_ID));

DetectResultBuilder::pass()
.build_plan(plan_builder.build())
Expand Down
2 changes: 1 addition & 1 deletion test_support/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use std::panic;
use std::path::PathBuf;
use std::time::{Duration, SystemTime};

const DEFAULT_BUILDER: &str = "heroku/builder:22";
const DEFAULT_BUILDER: &str = "heroku/builder:24";
pub const PORT: u16 = 8080;
pub const DEFAULT_RETRIES: u32 = 10;
pub const DEFAULT_RETRY_DELAY: Duration = Duration::from_secs(1);
Expand Down

0 comments on commit 9ad0264

Please sign in to comment.