Skip to main content

blue_build_process_management/drivers/
github_driver.rs

1use 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                    &timestamp,
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}