refactor: Move templates to their own crate (#83)
This PR logically separates out parts of the code to their own crates. This will be useful for future Tauri App development.
This commit is contained in:
parent
ce8f889dc2
commit
910e0434b6
34 changed files with 620 additions and 512 deletions
25
template/Cargo.toml
Normal file
25
template/Cargo.toml
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
[package]
|
||||
name = "blue-build-template"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
description.workspace = true
|
||||
repository.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
askama = { version = "0.12", features = ["serde-json", "serde-yaml"] }
|
||||
blue-build-recipe = { path = "../recipe" }
|
||||
blue-build-utils = { path = "../utils" }
|
||||
|
||||
log.workspace = true
|
||||
serde.workspace = true
|
||||
serde_yaml.workspace = true
|
||||
serde_json.workspace = true
|
||||
typed-builder.workspace = true
|
||||
uuid.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
137
template/src/lib.rs
Normal file
137
template/src/lib.rs
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
use std::{borrow::Cow, env, fs, path::Path, process};
|
||||
|
||||
use blue_build_recipe::Recipe;
|
||||
use blue_build_utils::constants::*;
|
||||
use log::{debug, error, trace};
|
||||
use typed_builder::TypedBuilder;
|
||||
use uuid::Uuid;
|
||||
|
||||
pub use askama::Template;
|
||||
|
||||
#[derive(Debug, Clone, Template, TypedBuilder)]
|
||||
#[template(path = "Containerfile.j2", escape = "none")]
|
||||
pub struct ContainerFileTemplate<'a> {
|
||||
recipe: &'a Recipe<'a>,
|
||||
|
||||
#[builder(setter(into))]
|
||||
recipe_path: &'a Path,
|
||||
|
||||
#[builder(setter(into))]
|
||||
build_id: Uuid,
|
||||
|
||||
#[builder(default)]
|
||||
export_script: ExportsTemplate,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Template)]
|
||||
#[template(path = "export.sh", escape = "none")]
|
||||
pub struct ExportsTemplate;
|
||||
|
||||
impl ExportsTemplate {
|
||||
fn print_script(&self) -> String {
|
||||
trace!("print_script({self})");
|
||||
|
||||
format!(
|
||||
"\"{}\"",
|
||||
self.render()
|
||||
.unwrap_or_else(|e| {
|
||||
error!("Failed to render export.sh script: {e}");
|
||||
process::exit(1);
|
||||
})
|
||||
.replace('\n', "\\n")
|
||||
.replace('\"', "\\\"")
|
||||
.replace('$', "\\$")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Template, TypedBuilder)]
|
||||
#[template(path = "github_issue.j2", escape = "md")]
|
||||
pub struct GithubIssueTemplate<'a> {
|
||||
#[builder(setter(into))]
|
||||
bb_version: Cow<'a, str>,
|
||||
|
||||
#[builder(setter(into))]
|
||||
build_rust_channel: Cow<'a, str>,
|
||||
|
||||
#[builder(setter(into))]
|
||||
build_time: Cow<'a, str>,
|
||||
|
||||
#[builder(setter(into))]
|
||||
git_commit_hash: Cow<'a, str>,
|
||||
|
||||
#[builder(setter(into))]
|
||||
os_name: Cow<'a, str>,
|
||||
|
||||
#[builder(setter(into))]
|
||||
os_version: Cow<'a, str>,
|
||||
|
||||
#[builder(setter(into))]
|
||||
pkg_branch_tag: Cow<'a, str>,
|
||||
|
||||
#[builder(setter(into))]
|
||||
recipe: Cow<'a, str>,
|
||||
|
||||
#[builder(setter(into))]
|
||||
rust_channel: Cow<'a, str>,
|
||||
|
||||
#[builder(setter(into))]
|
||||
rust_version: Cow<'a, str>,
|
||||
|
||||
#[builder(setter(into))]
|
||||
shell_name: Cow<'a, str>,
|
||||
|
||||
#[builder(setter(into))]
|
||||
shell_version: Cow<'a, str>,
|
||||
|
||||
#[builder(setter(into))]
|
||||
terminal_name: Cow<'a, str>,
|
||||
|
||||
#[builder(setter(into))]
|
||||
terminal_version: Cow<'a, str>,
|
||||
}
|
||||
|
||||
fn has_cosign_file() -> bool {
|
||||
trace!("has_cosign_file()");
|
||||
std::env::current_dir()
|
||||
.map(|p| p.join(COSIGN_PATH).exists())
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
fn print_containerfile(containerfile: &str) -> String {
|
||||
trace!("print_containerfile({containerfile})");
|
||||
debug!("Loading containerfile contents for {containerfile}");
|
||||
|
||||
let path = format!("config/containerfiles/{containerfile}/Containerfile");
|
||||
|
||||
let file = fs::read_to_string(&path).unwrap_or_else(|e| {
|
||||
error!("Failed to read file {path}: {e}");
|
||||
process::exit(1);
|
||||
});
|
||||
|
||||
debug!("Containerfile contents {path}:\n{file}");
|
||||
|
||||
file
|
||||
}
|
||||
|
||||
fn get_github_repo_owner() -> Option<String> {
|
||||
Some(env::var(GITHUB_REPOSITORY_OWNER).ok()?.to_lowercase())
|
||||
}
|
||||
|
||||
fn get_gitlab_registry_path() -> Option<String> {
|
||||
Some(
|
||||
format!(
|
||||
"{}/{}/{}",
|
||||
env::var(CI_REGISTRY).ok()?,
|
||||
env::var(CI_PROJECT_NAMESPACE).ok()?,
|
||||
env::var(CI_PROJECT_NAME).ok()?,
|
||||
)
|
||||
.to_lowercase(),
|
||||
)
|
||||
}
|
||||
|
||||
fn modules_exists() -> bool {
|
||||
let mod_path = Path::new("modules");
|
||||
mod_path.exists() && mod_path.is_dir()
|
||||
}
|
||||
59
template/templates/Containerfile.j2
Normal file
59
template/templates/Containerfile.j2
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
{%- let os_version = recipe.get_os_version() %}
|
||||
# This stage is responsible for holding onto
|
||||
# your config without copying it directly into
|
||||
# the final image
|
||||
FROM scratch as stage-config
|
||||
COPY ./config /config
|
||||
|
||||
# Copy modules
|
||||
# The default modules are inside blue-build/modules
|
||||
# Custom modules overwrite defaults
|
||||
FROM scratch as stage-modules
|
||||
COPY --from=ghcr.io/blue-build/modules:latest /modules /modules
|
||||
{%- if self::modules_exists() %}
|
||||
COPY ./modules /modules
|
||||
{%- endif %}
|
||||
|
||||
{%- include "modules/akmods/akmods.j2" %}
|
||||
|
||||
# This stage is responsible for holding onto
|
||||
# exports like the exports.sh
|
||||
FROM docker.io/alpine as stage-exports
|
||||
RUN printf {{ export_script.print_script() }} >> /exports.sh && chmod +x /exports.sh
|
||||
|
||||
FROM {{ recipe.base_image }}:{{ recipe.image_version }}
|
||||
|
||||
LABEL {{ blue_build_utils::constants::BUILD_ID_LABEL }}="{{ build_id }}"
|
||||
LABEL org.opencontainers.image.title="{{ recipe.name }}"
|
||||
LABEL org.opencontainers.image.description="{{ recipe.description }}"
|
||||
LABEL io.artifacthub.package.readme-url=https://raw.githubusercontent.com/blue-build/cli/main/README.md
|
||||
|
||||
ARG RECIPE={{ recipe_path.display() }}
|
||||
|
||||
{%- if let Some(repo_owner) = self::get_github_repo_owner() %}
|
||||
ARG IMAGE_REGISTRY=ghcr.io/{{ repo_owner }}
|
||||
{%- else if let Some(registry) = self::get_gitlab_registry_path() %}
|
||||
ARG IMAGE_REGISTRY={{ registry }}
|
||||
{%- else %}
|
||||
ARG IMAGE_REGISTRY=localhost
|
||||
{%- endif %}
|
||||
|
||||
{%- if self::has_cosign_file() %}
|
||||
COPY cosign.pub /usr/share/ublue-os/cosign.pub
|
||||
{%- endif %}
|
||||
|
||||
ARG CONFIG_DIRECTORY="/tmp/config"
|
||||
ARG IMAGE_NAME="{{ recipe.name }}"
|
||||
ARG BASE_IMAGE="{{ recipe.base_image }}"
|
||||
|
||||
{%- include "modules/modules.j2" %}
|
||||
|
||||
COPY --from=gcr.io/projectsigstore/cosign /ko-app/cosign /usr/bin/cosign
|
||||
COPY --from=ghcr.io/blue-build/cli:
|
||||
{%- if let Some(tag) = recipe.blue_build_tag -%}
|
||||
{{ tag }}
|
||||
{%- else -%}
|
||||
latest-installer
|
||||
{%- endif %} /out/bluebuild /usr/bin/bluebuild
|
||||
|
||||
RUN ostree container commit
|
||||
8
template/templates/export.sh
Normal file
8
template/templates/export.sh
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
get_yaml_array() {
|
||||
readarray -t "$1" < <(echo "$3" | yq -I=0 "$2")
|
||||
}
|
||||
|
||||
export -f get_yaml_array
|
||||
export OS_VERSION=$(grep -Po '(?<=VERSION_ID=)\d+' /usr/lib/os-release)
|
||||
35
template/templates/github_issue.j2
Normal file
35
template/templates/github_issue.j2
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
#### Current Behavior
|
||||
<!-- A clear and concise description of the behavior. -->
|
||||
|
||||
#### Expected Behavior
|
||||
<!-- A clear and concise description of what you expected to happen. -->
|
||||
|
||||
#### Additional context/Screenshots
|
||||
<!-- Add any other context about the problem here. If applicable, add screenshots to help explain. -->
|
||||
|
||||
#### Possible Solution
|
||||
<!--- Only if you have suggestions on a fix for the bug -->
|
||||
|
||||
#### Environment
|
||||
- Blue Build Version: {{ bb_version }}
|
||||
- Operating system: {{ os_name }} {{ os_version }}
|
||||
- Branch/Tag: {{ pkg_branch_tag }}
|
||||
- Git Commit Hash: {{ git_commit_hash }}
|
||||
|
||||
#### Shell
|
||||
- Name: {{ shell_name }}
|
||||
- Version: {{ shell_version }}
|
||||
- Terminal emulator: {{ terminal_name }} {{ terminal_version }}
|
||||
|
||||
#### Rust
|
||||
- Rust Version: {{ rust_version }}
|
||||
- Rust channel: {{ rust_channel }} {{ build_rust_channel }}
|
||||
- Build Time: {{ build_time }}
|
||||
|
||||
{%- if !recipe.is_empty() %}
|
||||
|
||||
#### Recipe:
|
||||
```yml
|
||||
{{ recipe }}
|
||||
```
|
||||
{%- endif %}
|
||||
7
template/templates/modules/akmods/akmods.j2
Normal file
7
template/templates/modules/akmods/akmods.j2
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
{%- for info in recipe.modules_ext.get_akmods_info_list(os_version) %}
|
||||
FROM scratch as stage-akmods-{{ info.stage_name }}
|
||||
COPY --from=ghcr.io/ublue-os/{{ info.images.0 }} /rpms /rpms
|
||||
{%- if let Some(nv_image) = info.images.1 %}
|
||||
COPY --from=ghcr.io/ublue-os/{{ nv_image }} /rpms /rpms
|
||||
{%- endif %}
|
||||
{%- endfor %}
|
||||
76
template/templates/modules/containerfile/README.md
Normal file
76
template/templates/modules/containerfile/README.md
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
# `containerfile`
|
||||
|
||||
:::caution
|
||||
Only compiler-based builds can use this module as it is built-in to the BlueBuild CLI tool.
|
||||
:::
|
||||
|
||||
The `containerfile` module is a tool for adding custom [`Containerfile`](https://github.com/containers/common/blob/main/docs/Containerfile.5.md) instructions for custom image builds. This is useful when you wish to use some feature directly available in a `Containerfile`, but not in a bash module, such as copying from other OCI images with `COPY --from`.
|
||||
|
||||
Since standard compiler-based BlueBuild image builds generate a `Containerfile` from your recipe, there is no need to manage it yourself. However, we know that we also have technical users that would like to have the ability to customize their `Containerfile`. This is where the `containerfile` module comes into play.
|
||||
|
||||
## Usage
|
||||
|
||||
### `snippets:`
|
||||
|
||||
The `snippets` property is the easiest to use when you just need to insert a few custom lines to the `Containerfile`. Each entry under the `snippets` property will be directly inserted into your final `Containerfile` for your build.
|
||||
|
||||
```yaml
|
||||
modules:
|
||||
- type: containerfile
|
||||
snippets:
|
||||
- COPY --from=docker.io/mikefarah/yq /usr/bin/yq /usr/bin/yq
|
||||
```
|
||||
|
||||
This makes it really easy to copy a file or program from another image.
|
||||
|
||||
:::note
|
||||
**NOTE:** Each entry of a snippet will be its own layer in the final `Containerfile`.
|
||||
:::
|
||||
|
||||
### `containerfiles:`
|
||||
|
||||
The `containerfiles` property allows you to tell the compiler which directory contains a `Containerfile` in `./config/containerfiles/`.
|
||||
|
||||
Below is an example of how a `containerfile` module would be used with the `containerfiles` property:
|
||||
|
||||
```yaml
|
||||
modules:
|
||||
- type: containerfile
|
||||
containerfiles:
|
||||
- example
|
||||
- subroutine
|
||||
```
|
||||
|
||||
In the example above, the compiler would look for these files:
|
||||
|
||||
- `./config/containerfiles/example/Containerfile`
|
||||
- `./config/containerfiles/subroutine/Containerfile`
|
||||
|
||||
You could then store files related to say the `subroutine` `Containerfile` in `./config/containerfiles/subroutine/` to keep it organized and portable for other recipes to use.
|
||||
|
||||
:::note
|
||||
**NOTE:** The instructions you add in your `Containerfile`'s each become a layer unlike other modules which are typically run as a single `RUN` command, thus creating only one layer.
|
||||
:::
|
||||
|
||||
### Order of operations
|
||||
|
||||
The order of operations is important in a `Containerfile`. There's a very simple set of rules for the order in this module:
|
||||
|
||||
- For each defined `containerfile` module:
|
||||
- First all `containerfiles:` are added to the main `Containerfile` in the order they are defined
|
||||
- Then all `snippets` are added to the main `Containerfile` in the order they are defined
|
||||
|
||||
If you wanted to have some `snippets` run before any `containerfiles` have, you will want to put them in their own module definition before the entry for `containerfiles`. For example:
|
||||
|
||||
```yaml
|
||||
modules:
|
||||
- type: containerfile
|
||||
snippets:
|
||||
- COPY --from=docker.io/mikefarah/yq /usr/bin/yq /usr/bin/yq
|
||||
- type: containerfile
|
||||
containerfiles:
|
||||
- example
|
||||
- subroutine
|
||||
```
|
||||
|
||||
In the example above, the `COPY` from the `snippets` will always come before the `containerfiles` "example" and "subroutine".
|
||||
10
template/templates/modules/containerfile/containerfile.j2
Normal file
10
template/templates/modules/containerfile/containerfile.j2
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
{%- if let Some(containerfiles) = module.get_containerfile_list() %}
|
||||
{%- for c in containerfiles %}
|
||||
{{ self::print_containerfile(c) }}
|
||||
{%- endfor %}
|
||||
{%- endif %}
|
||||
{%- if let Some(snippets) = module.get_containerfile_snippets() %}
|
||||
{%- for s in snippets %}
|
||||
{{ s }}
|
||||
{%- endfor %}
|
||||
{%- endif %}
|
||||
10
template/templates/modules/containerfile/module.yml
Normal file
10
template/templates/modules/containerfile/module.yml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
name: containerfile
|
||||
shortdesc: The containerfile module enables the addition of custom Containerfile instructions into the build process.
|
||||
readme: https://raw.githubusercontent.com/blue-build/cli/main/templates/modules/containerfile/README.md
|
||||
example: |
|
||||
type: containerfile
|
||||
snippets:
|
||||
- COPY ./config/example-dir/example-file.txt /usr/etc/example/
|
||||
containerfiles:
|
||||
- example
|
||||
- subroutine
|
||||
15
template/templates/modules/files/README.md
Normal file
15
template/templates/modules/files/README.md
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# `files`
|
||||
|
||||
The `files` module simplifies the process of copying files to the image during the build time. These files are sourced from the `config/files` directory, which is located at `/tmp/config/files` inside the image.
|
||||
|
||||
:::note
|
||||
If you want to place any files in `/etc/`, you should place them in `/usr/etc/` instead, which will be used to generate `/etc/` on a booted system. That is the proper directory for "system" configuration templates on atomic Fedora distros, whereas `/etc/` is meant for manual overrides and editing by the machine's admin AFTER installation! See issue https://github.com/blue-build/legacy-template/issues/28.
|
||||
:::
|
||||
|
||||
## Implementation differences between the legacy template and compiler-based builds
|
||||
|
||||
When using a compiler-based build (which is the recommended option for all users, so if you don't know what you're using you're probably using that), each instruction under `files:` creates its on layer in the final image using the `Containerfile` `COPY`-command. This module is entirely part of the recipe compiler.
|
||||
|
||||
When using a legacy template, all modules are combined into one layer in the final image. With a repo based on the legacy template, the bash version is used.
|
||||
|
||||
The API for both of these options remains exactly the same.
|
||||
5
template/templates/modules/files/files.j2
Normal file
5
template/templates/modules/files/files.j2
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
{%- if let Some(files) = module.get_files_list() %}
|
||||
{%- for (src, dest) in files %}
|
||||
COPY {{ src }} {{ dest }}
|
||||
{%- endfor %}
|
||||
{%- endif %}
|
||||
9
template/templates/modules/files/module.yml
Normal file
9
template/templates/modules/files/module.yml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
name: files
|
||||
shortdesc: The files module simplifies the process of copying files to the image during build time.
|
||||
readme: https://raw.githubusercontent.com/blue-build/cli/main/templates/modules/files/README.md
|
||||
example: |
|
||||
type: files
|
||||
files:
|
||||
- usr: /usr
|
||||
# usr: file/folder inside config/files/ to copy (config/files/usr/ in the repository)
|
||||
# /usr: destination on the final system
|
||||
29
template/templates/modules/modules.j2
Normal file
29
template/templates/modules/modules.j2
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
{%- for module in recipe.modules_ext.modules %}
|
||||
{%- if let Some(type) = module.module_type %}
|
||||
{%- if type == "containerfile" %}
|
||||
{%- include "modules/containerfile/containerfile.j2" %}
|
||||
{%- else if type == "files" %}
|
||||
{%- include "modules/files/files.j2" %}
|
||||
{%- else %}
|
||||
RUN \
|
||||
--mount=type=tmpfs,target=/tmp \
|
||||
--mount=type=tmpfs,target=/var \
|
||||
--mount=type=bind,from=docker.io/mikefarah/yq,src=/usr/bin/yq,dst=/usr/bin/yq \
|
||||
--mount=type=bind,from=stage-config,src=/config,dst=/tmp/config,rw \
|
||||
{%- if let Some(source) = module.source %}
|
||||
--mount=type=bind,from={{ source }},src=/modules,dst=/tmp/modules,rw \
|
||||
{%- else %}
|
||||
--mount=type=bind,from=stage-modules,src=/modules,dst=/tmp/modules,rw \
|
||||
{%- endif %}
|
||||
{%- if type == "akmods" %}
|
||||
--mount=type=bind,from=stage-akmods-{{ module.generate_akmods_info(os_version).stage_name }},src=/rpms,dst=/tmp/rpms,rw \
|
||||
{%- endif %}
|
||||
--mount=type=bind,from=stage-exports,src=/exports.sh,dst=/tmp/exports.sh \
|
||||
--mount=type=cache,dst=/var/cache/rpm-ostree,id=rpm-ostree-cache-{{ recipe.name }}-{{ recipe.image_version }},sharing=locked \
|
||||
chmod +x /tmp/modules/{{ type }}/{{ type }}.sh \
|
||||
&& source /tmp/exports.sh && /tmp/modules/{{ type }}/{{ type }}.sh '{{ module.print_module_context() }}'
|
||||
{%- endif %}
|
||||
{%- endif %}
|
||||
{%- endfor %}
|
||||
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue