blue_build_template/
lib.rs

1use std::{borrow::Cow, fs, path::Path, process};
2
3use blue_build_recipe::{MaybeVersion, Recipe};
4use blue_build_utils::{
5    constants::{CONFIG_PATH, CONTAINER_FILE, CONTAINERFILES_PATH, COSIGN_PUB_PATH, FILES_PATH},
6    secret::SecretMounts,
7};
8use bon::Builder;
9use chrono::Utc;
10use colored::control::ShouldColorize;
11use log::{debug, error, trace, warn};
12use uuid::Uuid;
13
14pub use askama::Template;
15
16#[derive(Debug, Clone, Template, Builder)]
17#[template(path = "Containerfile.j2", escape = "none", whitespace = "minimize")]
18#[builder(on(Cow<'_, str>, into))]
19pub struct ContainerFileTemplate<'a> {
20    #[builder(into)]
21    recipe: &'a Recipe<'a>,
22
23    #[builder(into)]
24    recipe_path: Cow<'a, Path>,
25
26    #[builder(into)]
27    build_id: Uuid,
28    os_version: u64,
29    registry: Cow<'a, str>,
30    build_scripts_image: Cow<'a, str>,
31    repo: Cow<'a, str>,
32    base_digest: Cow<'a, str>,
33    nushell_version: Option<&'a MaybeVersion>,
34}
35
36impl ContainerFileTemplate<'_> {
37    const fn should_install_nu(&self) -> bool {
38        match self.nushell_version {
39            None | Some(MaybeVersion::Version(_)) => true,
40            Some(MaybeVersion::None) => false,
41        }
42    }
43
44    fn get_nu_version(&self) -> String {
45        match self.nushell_version {
46            Some(MaybeVersion::None) | None => "default".to_string(),
47            Some(MaybeVersion::Version(version)) => version.to_string(),
48        }
49    }
50}
51
52#[derive(Debug, Clone, Template, Builder)]
53#[template(path = "github_issue.j2", escape = "md")]
54#[builder(on(Cow<'_, str>, into))]
55pub struct GithubIssueTemplate<'a> {
56    bb_version: Cow<'a, str>,
57    build_rust_channel: Cow<'a, str>,
58    build_time: Cow<'a, str>,
59    git_commit_hash: Cow<'a, str>,
60    os_name: Cow<'a, str>,
61    os_version: Cow<'a, str>,
62    pkg_branch_tag: Cow<'a, str>,
63    recipe: Cow<'a, str>,
64    rust_channel: Cow<'a, str>,
65    rust_version: Cow<'a, str>,
66    shell_name: Cow<'a, str>,
67    shell_version: Cow<'a, str>,
68    terminal_name: Cow<'a, str>,
69    terminal_version: Cow<'a, str>,
70}
71
72#[derive(Debug, Clone, Template, Builder)]
73#[template(path = "init/README.j2", escape = "md")]
74#[builder(on(Cow<'_, str>, into))]
75pub struct InitReadmeTemplate<'a> {
76    repo_name: Cow<'a, str>,
77    registry: Cow<'a, str>,
78    image_name: Cow<'a, str>,
79}
80
81#[derive(Debug, Clone, Template, Builder)]
82#[template(path = "init/gitlab-ci.yml.j2", escape = "none")]
83#[builder(on(Cow<'_, str>, into))]
84pub struct GitlabCiTemplate<'a> {
85    version: Cow<'a, str>,
86}
87
88fn has_cosign_file() -> bool {
89    trace!("has_cosign_file()");
90    std::env::current_dir()
91        .map(|p| p.join(COSIGN_PUB_PATH).exists())
92        .unwrap_or(false)
93}
94
95#[must_use]
96fn print_containerfile(containerfile: &str) -> String {
97    trace!("print_containerfile({containerfile})");
98    debug!("Loading containerfile contents for {containerfile}");
99
100    let legacy_path = Path::new(CONFIG_PATH);
101    let containerfiles_path = Path::new(CONTAINERFILES_PATH);
102
103    let path = if containerfiles_path.exists() && containerfiles_path.is_dir() {
104        containerfiles_path.join(format!("{containerfile}/{CONTAINER_FILE}"))
105    } else {
106        warn!(
107            "Use of {CONFIG_PATH} is deprecated for the containerfile module, please move your containerfile directories into {CONTAINERFILES_PATH}"
108        );
109        legacy_path.join(format!("containerfiles/{containerfile}/{CONTAINER_FILE}"))
110    };
111
112    let file = fs::read_to_string(&path).unwrap_or_else(|e| {
113        error!("Failed to read file {}: {e}", path.display());
114        process::exit(1);
115    });
116
117    debug!("Containerfile contents {}:\n{file}", path.display());
118
119    file
120}
121
122fn modules_exists() -> bool {
123    let mod_path = Path::new("modules");
124    mod_path.exists() && mod_path.is_dir()
125}
126
127fn files_dir_exists() -> bool {
128    let path = Path::new(FILES_PATH);
129    path.exists() && path.is_dir()
130}
131
132fn config_dir_exists() -> bool {
133    let path = Path::new(CONFIG_PATH);
134    let exists = path.exists() && path.is_dir();
135
136    if exists {
137        warn!(
138            "Use of the {CONFIG_PATH} directory is deprecated. Please move your non-recipe files into {FILES_PATH}"
139        );
140    }
141
142    exists
143}
144
145fn current_timestamp() -> String {
146    Utc::now().to_rfc3339()
147}
148
149fn should_color() -> bool {
150    ShouldColorize::from_env().should_colorize()
151}
152
153mod filters {
154    use askama::Values;
155
156    #[allow(clippy::unnecessary_wraps)]
157    pub fn replace<T>(input: T, _: &dyn Values, from: char, to: &str) -> askama::Result<String>
158    where
159        T: std::fmt::Display,
160    {
161        Ok(format!("{input}").replace(from, to))
162    }
163}