diff --git a/crates/ployz-build/src/execution.rs b/crates/ployz-build/src/execution.rs
index 1d645d0b09..f2856f6427 100644
--- a/crates/ployz-build/src/execution.rs
+++ b/crates/ployz-build/src/execution.rs
@@ -228,12 +228,14 @@ impl Admission {
#[cfg(test)]
mod tests {
use super::*;
+ use std::os::unix::fs::PermissionsExt;
#[test]
fn restart_removes_abandoned_uploads_but_keeps_unknown_ownership_unavailable() {
let root =
std::env::temp_dir().join(format!("ployz-build-restart-{}", uuid::Uuid::new_v4()));
std::fs::create_dir(&root).unwrap();
+ std::fs::set_permissions(&root, std::fs::Permissions::from_mode(0o700)).unwrap();
let policy = HostPolicy {
state_directory: root.clone(),
docker: root.join("docker"),
@@ -302,6 +304,7 @@ mod tests {
fn expired_admission_and_docker_report_configured_budget() {
let root = std::env::temp_dir().join(format!("ployz-budget-{}", uuid::Uuid::new_v4()));
std::fs::create_dir(&root).unwrap();
+ std::fs::set_permissions(&root, std::fs::Permissions::from_mode(0o700)).unwrap();
let policy = HostPolicy {
state_directory: root.clone(),
active_timeout: Duration::from_secs(5),
@@ -332,6 +335,7 @@ mod tests {
let root =
std::env::temp_dir().join(format!("ployz-upload-cancel-{}", uuid::Uuid::new_v4()));
std::fs::create_dir_all(root.join("build-upload")).unwrap();
+ std::fs::set_permissions(&root, std::fs::Permissions::from_mode(0o700)).unwrap();
std::fs::write(root.join("build-upload/existing"), "capture").unwrap();
let policy = HostPolicy {
state_directory: root.clone(),
diff --git a/crates/ployz-build/src/image_contexts.rs b/crates/ployz-build/src/image_contexts.rs
new file mode 100644
index 0000000000..861e244695
--- /dev/null
+++ b/crates/ployz-build/src/image_contexts.rs
@@ -0,0 +1,83 @@
+//! Bind completed Service outputs to BuildKit named contexts. The existing
+//! Machine image server supplies bytes directly; no external registry fallback.
+
+use crate::{BuildError, Request};
+use serde_norway::Value;
+use std::{fs, net::IpAddr};
+
+pub(crate) fn prepare(request: &Request<'_>) -> Result<(), BuildError> {
+ if request.image_contexts.is_empty() {
+ return Ok(());
+ }
+ let invalid = |message: &str| BuildError::Request(message.into());
+ let mut document: Value = serde_norway::from_slice(
+ &fs::read(request.working_dir.join(request.compose_file)).map_err(input_error)?,
+ )
+ .map_err(|_| invalid("invalid captured Build recipe"))?;
+ let services = document
+ .get_mut("services")
+ .and_then(Value::as_mapping_mut)
+ .ok_or_else(|| invalid("Build recipe has no services"))?;
+ for target in request.targets {
+ let build = services
+ .get_mut(Value::String(target.name.clone()))
+ .and_then(|service| service.get_mut("build"))
+ .and_then(Value::as_mapping_mut)
+ .ok_or_else(|| invalid("Build target has no recipe"))?;
+ let contexts = build.get("additional_contexts");
+ let entries = match contexts {
+ Some(Value::Mapping(values)) => values
+ .iter()
+ .map(|(name, value)| name.as_str().zip(value.as_str()))
+ .collect::