git_mirror/provider/
github.rs

1/*
2 * Copyright (c) 2017-2018 Pascal Bach
3 *
4 * SPDX-License-Identifier:     MIT
5 */
6
7// Used for error and debug logging
8use log::trace;
9
10// Used for github API access via HTTPS
11use reqwest::StatusCode;
12use reqwest::blocking::Client;
13use reqwest::header::{ACCEPT, HeaderMap, HeaderValue, USER_AGENT};
14
15use crate::provider::{Desc, Mirror, MirrorError, MirrorResult, Provider};
16
17pub struct GitHub {
18    pub url: String,
19    pub org: String,
20    pub use_http: bool,
21    pub private_token: Option<String>,
22    pub useragent: String,
23}
24
25/// A project from the GitLab API
26#[derive(Deserialize, Debug)]
27struct Project {
28    description: Option<String>,
29    url: String,
30    ssh_url: String,
31    clone_url: String,
32}
33
34impl Provider for GitHub {
35    fn get_label(&self) -> String {
36        format!("{}/orgs/{}", self.url, self.org)
37    }
38
39    fn get_mirror_repos(&self) -> Result<Vec<MirrorResult>, String> {
40        let client = Client::new();
41
42        let use_http = self.use_http;
43
44        let mut headers = HeaderMap::new();
45        // Github rejects requests without user agent
46        let useragent = HeaderValue::from_str(&self.useragent).expect("User agent invalid!");
47        headers.insert(USER_AGENT, useragent);
48        // Set the accept header to make sure the v3 api is used
49        let accept = HeaderValue::from_static("application/vnd.github.v3+json");
50        headers.insert(ACCEPT, accept);
51
52        let url = format!("{}/orgs/{}/repos", self.url, self.org);
53        trace!("URL: {url}");
54
55        let res = client
56            .get(&url)
57            .headers(headers)
58            .send()
59            .map_err(|e| format!("Unable to connect to: {url} ({e})"))?;
60
61        if res.status() != StatusCode::OK {
62            if res.status() == StatusCode::UNAUTHORIZED {
63                return Err(format!(
64                    "API call received unautorized ({}) for: {}. \
65                     Please make sure the `GITHUB_PRIVATE_TOKEN` environment \
66                     variable is set.",
67                    res.status(),
68                    url
69                ));
70            } else {
71                return Err(format!(
72                    "API call received invalid status ({}) for : {}",
73                    res.status(),
74                    url
75                ));
76            }
77        }
78
79        let projects: Vec<Project> = serde_json::from_reader(res)
80            .map_err(|e| format!("Unable to parse response as JSON ({e:?})"))?;
81
82        let mut mirrors: Vec<MirrorResult> = Vec::new();
83
84        for p in projects {
85            match serde_yaml::from_str::<Desc>(&p.description.unwrap_or_default()) {
86                Ok(desc) => {
87                    if desc.skip {
88                        mirrors.push(Err(MirrorError::Skip(p.url)));
89                        continue;
90                    }
91                    trace!("{0} -> {1}", desc.origin, p.ssh_url);
92                    let destination = if use_http { p.clone_url } else { p.ssh_url };
93                    let m = Mirror {
94                        origin: desc.origin,
95                        destination,
96                        refspec: desc.refspec,
97                        lfs: desc.lfs,
98                    };
99                    mirrors.push(Ok(m));
100                }
101                Err(e) => {
102                    mirrors.push(Err(MirrorError::Description(p.url, e)));
103                }
104            }
105        }
106        Ok(mirrors)
107    }
108}