blue_build_process_management/drivers/
gitlab_driver.rs1use std::path::PathBuf;
2
3use blue_build_utils::{
4 constants::{
5 CI_COMMIT_REF_NAME, CI_COMMIT_SHORT_SHA, CI_DEFAULT_BRANCH, CI_MERGE_REQUEST_IID,
6 CI_PIPELINE_SOURCE, CI_PROJECT_NAME, CI_PROJECT_NAMESPACE, CI_PROJECT_URL, CI_REGISTRY,
7 CI_SERVER_HOST, CI_SERVER_PROTOCOL,
8 },
9 container::Tag,
10 string_vec,
11};
12use log::trace;
13
14#[cfg(not(test))]
15use blue_build_utils::get_env_var;
16
17#[cfg(test)]
18use blue_build_utils::test_utils::get_env_var;
19use miette::Result;
20
21use crate::drivers::Driver;
22
23use super::{CiDriver, opts::GenerateTagsOpts};
24
25pub struct GitlabDriver;
26
27impl CiDriver for GitlabDriver {
28 fn on_default_branch() -> bool {
29 get_env_var(CI_DEFAULT_BRANCH).is_ok_and(|default_branch| {
30 get_env_var(CI_COMMIT_REF_NAME).is_ok_and(|branch| default_branch == branch)
31 })
32 }
33
34 fn keyless_cert_identity() -> Result<String> {
35 Ok(format!(
36 "{}//.gitlab-ci.yml@refs/heads/{}",
37 get_env_var(CI_PROJECT_URL)?,
38 get_env_var(CI_DEFAULT_BRANCH)?,
39 ))
40 }
41
42 fn oidc_provider() -> miette::Result<String> {
43 Ok(format!(
44 "{}://{}",
45 get_env_var(CI_SERVER_PROTOCOL)?,
46 get_env_var(CI_SERVER_HOST)?,
47 ))
48 }
49
50 fn generate_tags(opts: GenerateTagsOpts) -> Result<Vec<Tag>> {
51 const MR_EVENT: &str = "merge_request_event";
52 let os_version = Driver::get_os_version().oci_ref(opts.oci_ref).call()?;
53 let timestamp = blue_build_utils::get_tag_timestamp();
54 let short_sha =
55 get_env_var(CI_COMMIT_SHORT_SHA).inspect(|v| trace!("{CI_COMMIT_SHORT_SHA}={v}"))?;
56 let ref_name = get_env_var(CI_COMMIT_REF_NAME)
57 .inspect(|v| trace!("{CI_COMMIT_REF_NAME}={v}"))?
58 .replace('/', "_");
59
60 let tags = match (
61 Self::on_default_branch(),
62 opts.alt_tags.as_ref(),
63 get_env_var(CI_MERGE_REQUEST_IID).inspect(|v| trace!("{CI_MERGE_REQUEST_IID}={v}")),
64 get_env_var(CI_PIPELINE_SOURCE).inspect(|v| trace!("{CI_PIPELINE_SOURCE}={v}")),
65 ) {
66 (true, None, _, _) => {
67 string_vec![
68 "latest",
69 ×tamp,
70 format!("{os_version}"),
71 format!("{timestamp}-{os_version}"),
72 format!("{short_sha}-{os_version}"),
73 ]
74 }
75 (true, Some(alt_tags), _, _) => alt_tags
76 .iter()
77 .flat_map(|alt| {
78 string_vec![
79 alt,
80 format!("{alt}-{os_version}"),
81 format!("{timestamp}-{alt}-{os_version}"),
82 format!("{short_sha}-{alt}-{os_version}"),
83 ]
84 })
85 .collect(),
86 (false, None, Ok(mr_iid), Ok(pipeline_source)) if pipeline_source == MR_EVENT => {
87 vec![
88 format!("mr-{mr_iid}-{os_version}"),
89 format!("{short_sha}-{os_version}"),
90 ]
91 }
92 (false, None, _, _) => {
93 vec![
94 format!("br-{ref_name}-{os_version}"),
95 format!("{short_sha}-{os_version}"),
96 ]
97 }
98 (false, Some(alt_tags), Ok(mr_iid), Ok(pipeline_source))
99 if pipeline_source == MR_EVENT =>
100 {
101 alt_tags
102 .iter()
103 .flat_map(|alt| {
104 vec![
105 format!("mr-{mr_iid}-{alt}-{os_version}"),
106 format!("{short_sha}-{alt}-{os_version}"),
107 ]
108 })
109 .collect()
110 }
111 (false, Some(alt_tags), _, _) => alt_tags
112 .iter()
113 .flat_map(|alt| {
114 vec![
115 format!("br-{ref_name}-{alt}-{os_version}"),
116 format!("{short_sha}-{alt}-{os_version}"),
117 ]
118 })
119 .collect(),
120 }
121 .into_iter()
122 .map(|tag| tag.parse())
123 .collect::<Result<Vec<Tag>>>()?;
124 trace!("{tags:?}");
125
126 Ok(tags)
127 }
128
129 fn get_repo_url() -> miette::Result<String> {
130 Ok(format!(
131 "{}://{}/{}/{}",
132 get_env_var(CI_SERVER_PROTOCOL)?,
133 get_env_var(CI_SERVER_HOST)?,
134 get_env_var(CI_PROJECT_NAMESPACE)?,
135 get_env_var(CI_PROJECT_NAME)?,
136 ))
137 }
138
139 fn get_registry() -> miette::Result<String> {
140 Ok(format!(
141 "{}/{}/{}",
142 get_env_var(CI_REGISTRY)?,
143 get_env_var(CI_PROJECT_NAMESPACE)?,
144 get_env_var(CI_PROJECT_NAME)?,
145 )
146 .to_lowercase())
147 }
148
149 fn default_ci_file_path() -> PathBuf {
150 PathBuf::from(".gitlab-ci.yml")
151 }
152}
153
154#[cfg(test)]
155mod test {
156 use blue_build_utils::{
157 constants::{
158 CI_COMMIT_REF_NAME, CI_COMMIT_SHORT_SHA, CI_DEFAULT_BRANCH, CI_MERGE_REQUEST_IID,
159 CI_PIPELINE_SOURCE, CI_PROJECT_NAME, CI_PROJECT_NAMESPACE, CI_REGISTRY, CI_SERVER_HOST,
160 CI_SERVER_PROTOCOL,
161 },
162 container::Tag,
163 platform::Platform,
164 string_vec,
165 test_utils::set_env_var,
166 };
167 use oci_client::Reference;
168 use rstest::rstest;
169
170 use crate::{
171 drivers::{CiDriver, opts::GenerateTagsOpts},
172 test::{TEST_TAG_1, TEST_TAG_2, TIMESTAMP},
173 };
174
175 use super::GitlabDriver;
176
177 const COMMIT_SHA: &str = "1234567";
178 const BR_REF_NAME: &str = "feature/test";
179 const BR_REF_NAME_CLEAN: &str = "feature_test";
180
181 fn setup_default_branch() {
182 setup();
183 set_env_var(CI_COMMIT_REF_NAME, "main");
184 }
185
186 fn setup_mr_branch() {
187 setup();
188 set_env_var(CI_MERGE_REQUEST_IID, "12");
189 set_env_var(CI_PIPELINE_SOURCE, "merge_request_event");
190 set_env_var(CI_COMMIT_REF_NAME, BR_REF_NAME);
191 }
192
193 fn setup_branch() {
194 setup();
195 set_env_var(CI_COMMIT_REF_NAME, BR_REF_NAME);
196 }
197
198 fn setup() {
199 set_env_var(CI_DEFAULT_BRANCH, "main");
200 set_env_var(CI_COMMIT_SHORT_SHA, COMMIT_SHA);
201 set_env_var(CI_REGISTRY, "registry.example.com");
202 set_env_var(CI_PROJECT_NAMESPACE, "test-project");
203 set_env_var(CI_PROJECT_NAME, "test");
204 set_env_var(CI_SERVER_PROTOCOL, "https");
205 set_env_var(CI_SERVER_HOST, "gitlab.example.com");
206 }
207
208 #[test]
209 fn get_registry() {
210 setup();
211
212 let registry = GitlabDriver::get_registry().unwrap();
213
214 assert_eq!(registry, "registry.example.com/test-project/test");
215 }
216
217 #[test]
218 fn on_default_branch_true() {
219 setup_default_branch();
220
221 assert!(GitlabDriver::on_default_branch());
222 }
223
224 #[test]
225 fn on_default_branch_false() {
226 setup_branch();
227
228 assert!(!GitlabDriver::on_default_branch());
229 }
230
231 #[test]
232 fn get_repo_url() {
233 setup();
234
235 let url = GitlabDriver::get_repo_url().unwrap();
236
237 assert_eq!(url, "https://gitlab.example.com/test-project/test");
238 }
239
240 #[rstest]
241 #[case::default_branch(
242 setup_default_branch,
243 None,
244 string_vec![
245 format!("{}-41", &*TIMESTAMP),
246 "latest",
247 &*TIMESTAMP,
248 format!("{COMMIT_SHA}-41"),
249 "41",
250 ],
251 )]
252 #[case::default_branch_alt_tags(
253 setup_default_branch,
254 Some(bon::vec![TEST_TAG_1, TEST_TAG_2]),
255 string_vec![
256 TEST_TAG_1,
257 format!("{TEST_TAG_1}-41"),
258 format!("{}-{TEST_TAG_1}-41", &*TIMESTAMP),
259 format!("{COMMIT_SHA}-{TEST_TAG_1}-41"),
260 TEST_TAG_2,
261 format!("{TEST_TAG_2}-41"),
262 format!("{}-{TEST_TAG_2}-41", &*TIMESTAMP),
263 format!("{COMMIT_SHA}-{TEST_TAG_2}-41"),
264 ],
265 )]
266 #[case::pr_branch(
267 setup_mr_branch,
268 None,
269 string_vec!["mr-12-41", format!("{COMMIT_SHA}-41")],
270 )]
271 #[case::pr_branch_alt_tags(
272 setup_mr_branch,
273 Some(bon::vec![TEST_TAG_1, TEST_TAG_2]),
274 string_vec![
275 format!("mr-12-{TEST_TAG_1}-41"),
276 format!("{COMMIT_SHA}-{TEST_TAG_1}-41"),
277 format!("mr-12-{TEST_TAG_2}-41"),
278 format!("{COMMIT_SHA}-{TEST_TAG_2}-41"),
279 ],
280 )]
281 #[case::branch(
282 setup_branch,
283 None,
284 string_vec![format!("{COMMIT_SHA}-41"), format!("br-{BR_REF_NAME_CLEAN}-41")],
285 )]
286 #[case::branch_alt_tags(
287 setup_branch,
288 Some(bon::vec![TEST_TAG_1, TEST_TAG_2]),
289 string_vec![
290 format!("br-{BR_REF_NAME_CLEAN}-{TEST_TAG_1}-41"),
291 format!("{COMMIT_SHA}-{TEST_TAG_1}-41"),
292 format!("br-{BR_REF_NAME_CLEAN}-{TEST_TAG_2}-41"),
293 format!("{COMMIT_SHA}-{TEST_TAG_2}-41"),
294 ],
295 )]
296 fn generate_tags(
297 #[case] setup: impl FnOnce(),
298 #[case] alt_tags: Option<Vec<String>>,
299 #[case] mut expected: Vec<String>,
300 ) {
301 setup();
302 expected.sort();
303 let expected: Vec<Tag> = expected
304 .into_iter()
305 .map(|tag| tag.parse().unwrap())
306 .collect();
307 let oci_ref: Reference = "ghcr.io/ublue-os/silverblue-main".parse().unwrap();
308 let alt_tags = alt_tags.map(|tags| {
309 tags.into_iter()
310 .map(|tag| tag.parse::<Tag>().unwrap())
311 .collect::<Vec<_>>()
312 });
313
314 let mut tags = GitlabDriver::generate_tags(
315 GenerateTagsOpts::builder()
316 .oci_ref(&oci_ref)
317 .maybe_alt_tags(alt_tags.as_deref())
318 .platform(Platform::LinuxAmd64)
319 .build(),
320 )
321 .unwrap();
322 tags.sort();
323
324 assert_eq!(tags, expected);
325 }
326}