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}