feat(modules)!: Support new modules based starting point template
This commit is contained in:
parent
731e1d7567
commit
85aadf73e5
9 changed files with 208 additions and 23 deletions
3
.helix/languages.toml
Normal file
3
.helix/languages.toml
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
[[language]]
|
||||||
|
name = "rust"
|
||||||
|
config = { cargo = { features = [ "modules" ], noDefaultFeatures = false } }
|
||||||
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -787,6 +787,7 @@ name = "ublue-rs"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"cfg-if",
|
||||||
"clap",
|
"clap",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_yaml",
|
"serde_yaml",
|
||||||
|
|
|
||||||
|
|
@ -12,13 +12,16 @@ exclude = [".gitlab-ci.yml"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.75"
|
anyhow = "1.0.75"
|
||||||
|
cfg-if = "1.0.0"
|
||||||
clap = { version = "4.4.4", features = ["derive"] }
|
clap = { version = "4.4.4", features = ["derive"] }
|
||||||
serde = { version = "1.0.188", features = ["derive"] }
|
serde = { version = "1.0.188", features = ["derive"] }
|
||||||
serde_yaml = "0.9.25"
|
serde_yaml = "0.9.25"
|
||||||
tera = "1.19.1"
|
tera = "1.19.1"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = ["modules"]
|
||||||
nightly = ["init", "build"]
|
nightly = ["init", "build"]
|
||||||
init = []
|
init = []
|
||||||
build = []
|
build = []
|
||||||
|
modules = []
|
||||||
|
legacy = []
|
||||||
|
|
|
||||||
12
README.md
12
README.md
|
|
@ -10,19 +10,27 @@ Right now the only way to install this tool is to use `cargo`.
|
||||||
cargo install --locked ublue-rs
|
cargo install --locked ublue-rs
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Legacy Starting Point
|
||||||
|
|
||||||
|
If you want to install the tool for use with the legacy setup of the starting point template, you can install it with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo install --locked --features legacy --no-default-features ublue-rs
|
||||||
|
```
|
||||||
|
|
||||||
## How to use
|
## How to use
|
||||||
|
|
||||||
Once you have the CLI tool installed, you can run the following to pull in your recipe file to generate a `Containerfile`.
|
Once you have the CLI tool installed, you can run the following to pull in your recipe file to generate a `Containerfile`.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
ublue template recipe.yml -o Containerfile
|
ublue template -o <CONTAINERFILE> <RECIPE_FILE>
|
||||||
```
|
```
|
||||||
|
|
||||||
You can then use this with `podman` to build and publish your image. Further options can be viewed by running `ublue --help`
|
You can then use this with `podman` to build and publish your image. Further options can be viewed by running `ublue --help`
|
||||||
|
|
||||||
## Future Features
|
## Future Features
|
||||||
|
|
||||||
- [ ] Update to the most recent stable style of the [starting point](https://github.com/ublue-os/startingpoint/tree/template) template
|
- [x] Update to the most recent stable style of the [starting point](https://github.com/ublue-os/startingpoint/tree/template) template
|
||||||
- [ ] Create an init command to create a repo for you to start out
|
- [ ] Create an init command to create a repo for you to start out
|
||||||
- [ ] Setup the project to allow installing with `binstall`
|
- [ ] Setup the project to allow installing with `binstall`
|
||||||
- [ ] Create an install script for easy install for users without `cargo`
|
- [ ] Create an install script for easy install for users without `cargo`
|
||||||
|
|
|
||||||
|
|
@ -13,13 +13,14 @@ fn main() -> Result<()> {
|
||||||
} => {
|
} => {
|
||||||
let (tera, context) = ublue_rs::setup_tera(recipe, containerfile)?;
|
let (tera, context) = ublue_rs::setup_tera(recipe, containerfile)?;
|
||||||
let output_str = tera.render("Containerfile", &context)?;
|
let output_str = tera.render("Containerfile", &context)?;
|
||||||
|
|
||||||
if let Some(output) = output {
|
if let Some(output) = output {
|
||||||
std::fs::write(output, output_str)?;
|
std::fs::write(output, output_str)?;
|
||||||
} else {
|
} else {
|
||||||
println!("{output_str}");
|
println!("{output_str}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[cfg(init)]
|
#[cfg(feature = "init")]
|
||||||
CommandArgs::Init { dir } => {
|
CommandArgs::Init { dir } => {
|
||||||
let base_dir = match dir {
|
let base_dir = match dir {
|
||||||
Some(dir) => dir,
|
Some(dir) => dir,
|
||||||
|
|
@ -28,7 +29,7 @@ fn main() -> Result<()> {
|
||||||
|
|
||||||
ublue_rs::init::initialize_directory(base_dir);
|
ublue_rs::init::initialize_directory(base_dir);
|
||||||
}
|
}
|
||||||
#[cfg(build)]
|
#[cfg(feature = "build")]
|
||||||
CommandArgs::Build { containerfile: _ } => {
|
CommandArgs::Build { containerfile: _ } => {
|
||||||
println!("Not yet implemented!");
|
println!("Not yet implemented!");
|
||||||
todo!();
|
todo!();
|
||||||
|
|
|
||||||
106
src/lib.rs
106
src/lib.rs
|
|
@ -1,20 +1,46 @@
|
||||||
|
//! The root library for ublue-rs.
|
||||||
|
//!
|
||||||
|
//! This module consists of the args for the cli as well as the
|
||||||
|
//! initial entrypoint for setting up tera to properly template
|
||||||
|
//! the Containerfile. There is support for legacy starting point
|
||||||
|
//! recipes using the feature flag 'legacy' and support for the newest
|
||||||
|
//! starting point setup using the 'modules' feature flag. You will not want
|
||||||
|
//! to use both features at the same time. For now the 'legacy' feature
|
||||||
|
//! is the default feature until modules works 1-1 with ublue starting point.
|
||||||
|
|
||||||
|
#[cfg(all(feature = "legacy", feature = "modules"))]
|
||||||
|
compile_error!("Both 'legacy' and 'modules' features cannot be used at the same time.");
|
||||||
|
|
||||||
|
#[cfg(feature = "init")]
|
||||||
|
pub mod init;
|
||||||
|
|
||||||
|
#[cfg(feature = "legacy")]
|
||||||
|
pub mod recipe;
|
||||||
|
|
||||||
|
#[cfg(feature = "modules")]
|
||||||
|
pub mod module_recipe;
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
fs::{self, read_dir, read_to_string},
|
fs::{self, read_to_string},
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use cfg_if;
|
||||||
use clap::{Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
use recipe::Recipe;
|
use tera::{Context, Tera};
|
||||||
use tera::{from_value, Context, Tera};
|
|
||||||
|
|
||||||
pub const DEFAULT_CONTAINERFILE: &'static str =
|
cfg_if::cfg_if! {
|
||||||
include_str!("../templates/starting_point.template");
|
if #[cfg(feature = "legacy")] {
|
||||||
|
use recipe::Recipe;
|
||||||
#[cfg(init)]
|
use std::fs::read_dir;
|
||||||
pub mod init;
|
pub const DEFAULT_CONTAINERFILE: &str = include_str!("../templates/Containerfile.legacy");
|
||||||
pub mod recipe;
|
} else if #[cfg(feature = "modules")] {
|
||||||
|
use module_recipe::Recipe;
|
||||||
|
pub const DEFAULT_CONTAINERFILE: &str = include_str!("../templates/Containerfile.modules");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
#[command(name = "Ublue Builder", author, version, about, long_about = None)]
|
#[command(name = "Ublue Builder", author, version, about, long_about = None)]
|
||||||
|
|
@ -41,7 +67,7 @@ pub enum CommandArgs {
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Initialize a new Ublue Starting Point repo
|
/// Initialize a new Ublue Starting Point repo
|
||||||
#[cfg(init)]
|
#[cfg(feature = "init")]
|
||||||
Init {
|
Init {
|
||||||
/// The directory to extract the files into. Defaults to the current directory
|
/// The directory to extract the files into. Defaults to the current directory
|
||||||
#[arg()]
|
#[arg()]
|
||||||
|
|
@ -49,7 +75,7 @@ pub enum CommandArgs {
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Build an image from a Containerfile
|
/// Build an image from a Containerfile
|
||||||
#[cfg(build)]
|
#[cfg(feature = "build")]
|
||||||
Build {
|
Build {
|
||||||
#[arg()]
|
#[arg()]
|
||||||
containerfile: String,
|
containerfile: String,
|
||||||
|
|
@ -58,33 +84,77 @@ pub enum CommandArgs {
|
||||||
|
|
||||||
pub fn setup_tera(recipe: String, containerfile: Option<PathBuf>) -> Result<(Tera, Context)> {
|
pub fn setup_tera(recipe: String, containerfile: Option<PathBuf>) -> Result<(Tera, Context)> {
|
||||||
let recipe_de =
|
let recipe_de =
|
||||||
serde_yaml::from_str::<Recipe>(fs::read_to_string(PathBuf::from(&recipe))?.as_str())?
|
serde_yaml::from_str::<Recipe>(fs::read_to_string(PathBuf::from(&recipe))?.as_str())?;
|
||||||
.process_repos();
|
|
||||||
|
#[cfg(feature = "legacy")]
|
||||||
|
let recipe_de = recipe_de.process_repos();
|
||||||
|
|
||||||
let mut context = Context::from_serialize(recipe_de)?;
|
let mut context = Context::from_serialize(recipe_de)?;
|
||||||
context.insert("recipe", &recipe);
|
context.insert("recipe", &recipe);
|
||||||
|
|
||||||
let mut tera = Tera::default();
|
let mut tera = Tera::default();
|
||||||
|
|
||||||
match containerfile {
|
match containerfile {
|
||||||
Some(containerfile) => {
|
Some(containerfile) => {
|
||||||
tera.add_raw_template("Containerfile", &read_to_string(containerfile)?)?
|
tera.add_raw_template("Containerfile", &read_to_string(containerfile)?)?
|
||||||
}
|
}
|
||||||
None => tera.add_raw_template("Containerfile", DEFAULT_CONTAINERFILE)?,
|
None => tera.add_raw_template("Containerfile", DEFAULT_CONTAINERFILE)?,
|
||||||
}
|
}
|
||||||
|
|
||||||
tera.register_function(
|
tera.register_function(
|
||||||
"print_containerfile",
|
"print_containerfile",
|
||||||
|args: &HashMap<String, tera::Value>| -> tera::Result<tera::Value> {
|
|args: &HashMap<String, tera::Value>| -> tera::Result<tera::Value> {
|
||||||
match args.get("containerfile") {
|
match args.get("containerfile") {
|
||||||
Some(v) => match from_value::<String>(v.clone()) {
|
Some(v) => match v.as_str() {
|
||||||
Ok(containerfile) => {
|
Some(containerfile) => Ok(read_to_string(format!(
|
||||||
Ok(read_to_string(format!("containerfiles/{containerfile}"))?.into())
|
"containerfiles/{containerfile}/Containerfile"
|
||||||
}
|
))?
|
||||||
Err(_) => Err("Arg containerfile wasn't a string".into()),
|
.into()),
|
||||||
|
None => Err("Arg containerfile wasn't a string".into()),
|
||||||
},
|
},
|
||||||
None => Err("Needs the argument 'containerfile'".into()),
|
None => Err("Needs the argument 'containerfile'".into()),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
#[cfg(feature = "modules")]
|
||||||
|
tera.register_function(
|
||||||
|
"print_module_context",
|
||||||
|
|args: &HashMap<String, tera::Value>| -> tera::Result<tera::Value> {
|
||||||
|
match args.get("module") {
|
||||||
|
Some(v) => Ok(match serde_yaml::to_string(v) {
|
||||||
|
Ok(s) => s.escape_default().collect::<String>(),
|
||||||
|
Err(_) => "Unable to serialize".into(),
|
||||||
|
}
|
||||||
|
.into()),
|
||||||
|
None => Err("Needs the argument 'module'".into()),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
#[cfg(feature = "modules")]
|
||||||
|
tera.register_function(
|
||||||
|
"get_module_from_file",
|
||||||
|
|args: &HashMap<String, tera::Value>| -> tera::Result<tera::Value> {
|
||||||
|
match args.get("file") {
|
||||||
|
Some(v) => {
|
||||||
|
let file = match v.as_str() {
|
||||||
|
Some(s) => s,
|
||||||
|
None => return Err("Property 'from-file' must be a string".into()),
|
||||||
|
};
|
||||||
|
match serde_yaml::from_str::<tera::Value>(
|
||||||
|
read_to_string(format!("config/{file}"))?.as_str(),
|
||||||
|
) {
|
||||||
|
Ok(context) => Ok(context),
|
||||||
|
Err(_) => Err(format!("Unable to deserialize file {file}").into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => Err("Needs the argument 'file'".into()),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
#[cfg(feature = "legacy")]
|
||||||
tera.register_function(
|
tera.register_function(
|
||||||
"print_autorun_scripts",
|
"print_autorun_scripts",
|
||||||
|args: &HashMap<String, tera::Value>| -> tera::Result<tera::Value> {
|
|args: &HashMap<String, tera::Value>| -> tera::Result<tera::Value> {
|
||||||
|
|
|
||||||
40
src/module_recipe.rs
Normal file
40
src/module_recipe.rs
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_yaml::Value;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct Recipe {
|
||||||
|
pub name: String,
|
||||||
|
|
||||||
|
#[serde(alias = "base-image")]
|
||||||
|
pub base_image: String,
|
||||||
|
|
||||||
|
#[serde(alias = "image-version")]
|
||||||
|
pub image_version: u16,
|
||||||
|
|
||||||
|
pub modules: Vec<Module>,
|
||||||
|
|
||||||
|
pub containerfiles: Option<Containerfiles>,
|
||||||
|
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub extra: HashMap<String, Value>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct Module {
|
||||||
|
#[serde(rename = "type")]
|
||||||
|
pub module_type: Option<String>,
|
||||||
|
|
||||||
|
#[serde(rename = "from-file")]
|
||||||
|
pub from_file: Option<String>,
|
||||||
|
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub config: HashMap<String, Value>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct Containerfiles {
|
||||||
|
pub pre: Option<Vec<String>>,
|
||||||
|
pub post: Option<Vec<String>>,
|
||||||
|
}
|
||||||
59
templates/Containerfile.modules
Normal file
59
templates/Containerfile.modules
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
FROM {{ base_image }}:{{ image_version }}
|
||||||
|
|
||||||
|
ARG RECIPE={{ recipe }}
|
||||||
|
|
||||||
|
# Copy the bling from ublue-os/bling into tmp, to be installed later by the bling module
|
||||||
|
# Feel free to remove these lines if you want to speed up image builds and don't want any bling
|
||||||
|
COPY --from=ghcr.io/ublue-os/bling:latest /rpms /tmp/bling/rpms
|
||||||
|
COPY --from=ghcr.io/ublue-os/bling:latest /files /tmp/bling/files
|
||||||
|
|
||||||
|
COPY --from=docker.io/mikefarah/yq /usr/bin/yq /usr/bin/yq
|
||||||
|
|
||||||
|
COPY --from=gcr.io/projectsigstore/cosign /ko-app/cosign /usr/bin/cosign
|
||||||
|
|
||||||
|
COPY config /tmp/config/
|
||||||
|
|
||||||
|
# Copy modules
|
||||||
|
# The default modules are inside ublue-os/bling
|
||||||
|
COPY --from=ghcr.io/ublue-os/bling:latest /modules /tmp/modules/
|
||||||
|
# Custom modules overwrite defaults
|
||||||
|
COPY modules /tmp/modules/
|
||||||
|
|
||||||
|
RUN echo "#!/usr/bin/env bash" >> /tmp/exports.sh
|
||||||
|
RUN echo 'get_yaml_array() { readarray "$1" < <(echo "$3" | yq -I=0 "$2"); }; export -f get_yaml_array' >> /tmp/exports.sh
|
||||||
|
RUN chmod +x /tmp/exports.sh
|
||||||
|
|
||||||
|
ARG CONFIG_DIRECTORY="/tmp/config"
|
||||||
|
ARG IMAGE_NAME="{{ name }}"
|
||||||
|
ARG BASE_IMAGE="{{ base_image }}"
|
||||||
|
|
||||||
|
{% if containerfiles and containerfiles.pre %}
|
||||||
|
# Pre: Containerfiles
|
||||||
|
{% for containerfile in containerfiles.pre %}
|
||||||
|
{{ print_containerfile(containerfile = containerfile) }}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% macro run_modules(module) %}
|
||||||
|
{% if module.type %}
|
||||||
|
RUN chmod +x /tmp/modules/{{ module.type }}/{{ module.type }}.sh
|
||||||
|
RUN source /tmp/exports.sh && OS_VERSION="$(grep -Po '(?<=VERSION_ID=)\d+' /usr/lib/os-release)" /tmp/modules/{{ module.type }}/{{ module.type }}.sh $(echo -e "{{ print_module_context(module = module) }}")
|
||||||
|
{% elif module["from-file"] %}
|
||||||
|
{% set extra_module = get_module_from_file(file = module["from-file"]) %}
|
||||||
|
{% for m in extra_module.modules %}
|
||||||
|
{{ self::run_modules(module = m) }}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% endmacro run_modules %}
|
||||||
|
|
||||||
|
{% for module in modules %}
|
||||||
|
{{ self::run_modules(module = module) }}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{% if containerfiles and containerfiles.post %}
|
||||||
|
{% for containerfile in containerfiles.post %}
|
||||||
|
{{ print_containerfile(containerfile = containerfile) }}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
RUN rm -rf /tmp/* /var/* && ostree container commit
|
||||||
Loading…
Add table
Add a link
Reference in a new issue