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