Skip to content
Open
Show file tree
Hide file tree
Changes from 5 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
2 changes: 2 additions & 0 deletions recipe/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ mod akmods_info;
mod maybe_version;
mod module;
mod module_ext;
mod mount;
mod recipe;
mod stage;
mod stages_ext;
Expand All @@ -15,6 +16,7 @@ pub use akmods_info::*;
pub use maybe_version::*;
pub use module::*;
pub use module_ext::*;
pub use mount::*;
pub use recipe::*;
pub use stage::*;
pub use stages_ext::*;
Expand Down
6 changes: 5 additions & 1 deletion recipe/src/module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use miette::{Result, bail};
use serde::{Deserialize, Serialize};
use serde_yaml::Value;

use crate::{AkmodsInfo, ModuleExt, base_recipe_path};
use crate::{AkmodsInfo, ModuleExt, base_recipe_path, mount::Mount};

mod type_ver;

Expand Down Expand Up @@ -39,6 +39,10 @@ pub struct ModuleRequiredFields {
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub secrets: Vec<Secret>,

#[builder(default)]
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub mounts: Vec<Mount>,

#[serde(flatten)]
#[builder(default, into)]
pub config: IndexMap<String, Value>,
Expand Down
112 changes: 112 additions & 0 deletions recipe/src/mount.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize, Clone)]
pub enum MountCacheSharing {
/// The cache is shared between all builds.
#[serde(rename = "shared")]
Shared,

/// The cache is private to the current build.
#[serde(rename = "private")]
Private,

/// The cache is shared between builds, but only one build can use it at a time.
#[serde(rename = "locked")]
Locked,
}
impl std::fmt::Display for MountCacheSharing {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Shared => write!(f, "shared"),
Self::Private => write!(f, "private"),
Self::Locked => write!(f, "locked"),
}
}
}

#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(tag = "type")]
pub enum Mount {
/// A bind mount, which mounts a file or directory from the host.
#[serde(rename = "bind")]
Bind {
/// The source path on the host.
source: String,
/// The destination path in the container.
destination: String,
/// Whether the mount is read-only.
#[serde(default, rename = "readonly")]
readonly: bool,
},

/// A tmpfs mount, which mounts a temporary file system in memory.
#[serde(rename = "tmpfs")]
Tmpfs {
/// The destination path.
destination: String,
/// The size of the tmpfs. Can be specified in bytes or with a suffix (e.g. "100m" for 100 megabytes).
#[serde(skip_serializing_if = "Option::is_none")]
size: Option<String>,
},

/// A cache mount, which mounts a cache directory that can be shared between builds.
#[serde(rename = "cache")]
Cache {
/// The destination path.
destination: String,
/// The cache ID, which is used to identify the cache.
#[serde(skip_serializing_if = "Option::is_none")]
id: Option<String>,
/// The cache sharing mode.
#[serde(skip_serializing_if = "Option::is_none")]
sharing: Option<MountCacheSharing>,
},
}

impl std::fmt::Display for Mount {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Bind {
source,
destination,
readonly,
} => {
write!(f, "type=bind,source={source},dst={destination}")?;
if *readonly {
write!(f, ",readonly")?;
}
}
Self::Tmpfs { destination, size } => {
write!(f, "type=tmpfs,dst={destination}")?;
if let Some(size) = size {
write!(f, ",size={size}")?;
}
}
Self::Cache {
destination,
id,
sharing,
} => {
write!(f, "type=cache")?;
if let Some(sharing) = sharing {
write!(f, ",sharing={sharing}")?;
}
write!(f, ",dst={destination}")?;
if let Some(id) = id {
write!(f, ",id={id}")?;
}
}
}
Ok(())
}
}

impl Mount {
#[must_use]
pub const fn oci_suffix(&self) -> &'static str {
match self {
Self::Bind { .. } => ",z",
_ => "",
}
}
}
7 changes: 6 additions & 1 deletion recipe/src/recipe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use miette::{Context, IntoDiagnostic, Result};
use oci_client::Reference;
use serde::{Deserialize, Serialize};

use crate::{Module, ModuleExt, StagesExt, maybe_version::MaybeVersion};
use crate::{Module, ModuleExt, StagesExt, maybe_version::MaybeVersion, mount::Mount};

/// The build recipe.
///
Expand Down Expand Up @@ -90,6 +90,11 @@ pub struct Recipe {
/// This hashmap provides custom labels from ther use to the image
#[serde(skip_serializing_if = "Option::is_none")]
pub labels: Option<HashMap<String, String>>,

/// The mounts to add to the image.
#[serde(skip_serializing_if = "Vec::is_empty")]
Comment thread
Aex12 marked this conversation as resolved.
Outdated
#[builder(default)]
pub mounts: Vec<Mount>,
}

impl Recipe {
Expand Down
21 changes: 19 additions & 2 deletions template/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::{borrow::Cow, collections::BTreeMap, fs, path::Path, process};
use std::{borrow::Cow, collections::BTreeMap, fmt::Write, fs, path::Path, process};

use blue_build_recipe::{MaybeVersion, Recipe};
use blue_build_recipe::{MaybeVersion, ModuleRequiredFields, Recipe};
use blue_build_utils::{
constants::{CONFIG_PATH, CONTAINER_FILE, CONTAINERFILES_PATH, COSIGN_PUB_PATH, FILES_PATH},
container::Tag,
Expand Down Expand Up @@ -80,6 +80,23 @@ impl ContainerFileTemplate<'_> {
}
)
}

fn user_mounts(&self, module: &ModuleRequiredFields) -> String {
let mut s = self.recipe.mounts.iter().chain(module.mounts.iter()).fold(
String::new(),
|mut acc, mount| {
let suffix: &str = match self.build_engine {
BuildEngine::Oci => mount.oci_suffix(),
BuildEngine::Docker => "",
};

writeln!(acc, "--mount={mount}{suffix} \\").unwrap();
acc
},
);
s.pop(); // Avoid trailing newline
s
}
}

#[derive(Debug, Clone, Template, Builder)]
Expand Down
3 changes: 3 additions & 0 deletions template/templates/modules/modules.j2
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ RUN \
{{ cache_mount }},dst=/var/cache/apt,id=apt-{{ cache_name }} \
{{ cache_mount }},dst=/var/cache/pacman,id=pacman-{{ cache_name }} \

{#- User defined mounts #}
{{ user_mounts(&module) }}

{#- Secret environment variables #}
{%- for secret_var in module.secrets.envs() %}
{{ secret_var }} \
Expand Down
12 changes: 12 additions & 0 deletions test-files/recipes/recipe-pass.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,15 @@ modules:
from: fedora-test
src: /test.txt
dest: /
- type: test-module
source: local
mounts:
- type: tmpfs
destination: /tmp/test
mounts:
- type: cache
id: downloads-cli-test-43
destination: /var/cache/downloads
sharing: locked
- type: tmpfs
destination: /tmp
116 changes: 116 additions & 0 deletions test-files/schema/recipe-v1.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,13 @@
"$ref": "#/$defs/ModuleEntry"
},
"description": "A list of [modules](https://blue-build.org/reference/module/) that is executed in order. Multiple of the same module can be included.\n\nEach item in this list should have at least a `type:` or be specified to be included from an external file in the `recipes/` directory with `from-file:`."
},
"mounts": {
"type": "array",
"items": {
"$ref": "#/$defs/Mount"
},
"description": "A list of mounts that will be mounted in each RUN stage."
}
},
"required": [
Expand Down Expand Up @@ -78,9 +85,118 @@
},
{
"$ref": "#/$defs/ImportedModule"
},
{
"type": "object",
"additionalProperties": false,
"properties": {
"mounts": {
"type": "array",
"items": {
"$ref": "#/$defs/Mount"
},
"description": "A list of mounts that will be mounted for this module."
}
}
}
]
},
"Mount": {
"oneOf": [
{
"$ref": "#/$defs/MountBind"
},
{
"$ref": "#/$defs/MountTmpFs"
},
{
"$ref": "#/$defs/MountCache"
}
]
},
"MountBind": {
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "bind",
"description": "A bind mount from the host system."
},
"source": {
"type": "string",
"description": "The source path on the host system to mount."
},
"destination": {
"type": "string",
"description": "The destination path to mount to."
},
"readonly": {
"type": "boolean",
"description": "Whether the mount should be read-only."
}
},
"required": [
"type",
"source",
"destination"
]
},
"MountTmpFs": {
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "tmpfs",
"description": "A temporary file system mount."
},
"destination": {
"type": "string",
"description": "The destination path to mount to."
},
"size": {
"type": "string",
"description": "The size of the tmpfs mount."
}
},
"required": [
"type",
"destination"
]
},
"MountCache": {
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "cache",
"description": "A cache mount."
},
"destination": {
"type": "string",
"description": "The destination path to mount to."
},
"id": {
"type": "string",
"description": "An identifier for the cache."
},
"sharing": {
"$ref": "#/$defs/MountCacheSharing",
"description": "The sharing mode of the cache."
}
},
"required": [
"type",
"destination"
]
},
"MountCacheSharing": {
"type": "string",
"enum": [
"shared",
"private",
"locked"
]
},
"ImportedModule": {
"type": "object",
"properties": {
Expand Down
Loading