auth_tarball_from_git/
git.rs

1use crate::errors::*;
2use std::path::Path;
3use std::process::Stdio;
4use tokio::fs;
5use tokio::process::Command;
6
7pub async fn clone(folder: &Path, url: &str, tag: &str) -> Result<()> {
8    let cmd = Command::new("git")
9        .arg("clone")
10        .arg("-q")
11        .arg("--bare")
12        .arg("--depth=1")
13        .arg("--branch")
14        .arg(tag)
15        .arg("--")
16        .arg(url)
17        .arg(folder)
18        .spawn()
19        .context("Failed to run git clone")?;
20
21    let out = cmd.wait_with_output().await?;
22    if !out.status.success() {
23        bail!("Process (git clone) exited with error: {:?}", out.status);
24    }
25
26    Ok(())
27}
28
29pub struct Tag {
30    data: Vec<u8>,
31    sig_pos: Option<usize>,
32}
33
34impl Tag {
35    pub fn data(&self) -> &[u8] {
36        if let Some(pos) = self.sig_pos {
37            &self.data[..pos]
38        } else {
39            &self.data
40        }
41    }
42
43    pub fn sig(&self) -> Option<&[u8]> {
44        if let Some(pos) = self.sig_pos {
45            Some(&self.data[pos..])
46        } else {
47            None
48        }
49    }
50
51    pub async fn verify(&self, keyrings: &[&Path]) -> Result<()> {
52        let obj = self.data();
53        let sig = self
54            .sig()
55            .ok_or_else(|| anyhow!("Failed to find signature in tag"))?;
56
57        let tmp_dir = tempfile::Builder::new()
58            .prefix("auth-from-git-")
59            .tempdir()?;
60        let path = tmp_dir.path();
61        let obj_path = path.join("obj");
62        let sig_path = path.join("sig");
63
64        fs::write(&obj_path, obj).await?;
65        fs::write(&sig_path, sig).await?;
66
67        let mut cmd = Command::new("sqv");
68
69        for keyring in keyrings {
70            cmd.arg("--keyring");
71            cmd.arg(keyring);
72        }
73
74        let cmd = cmd
75            .arg("--")
76            .arg(sig_path)
77            .arg(obj_path)
78            .stdout(Stdio::null())
79            .spawn()
80            .context("Failed to run sqv")?;
81
82        let out = cmd.wait_with_output().await?;
83        if !out.status.success() {
84            bail!("Process (sqv) exited with error: {:?}", out.status);
85        }
86
87        Ok(())
88    }
89}
90
91pub async fn read_tag(folder: &Path, tag: &str) -> Result<Tag> {
92    let tag_bytes = cat_tag(folder, tag).await?;
93    let needle = b"-----BEGIN PGP SIGNATURE-----\n";
94    let pos = tag_bytes
95        .windows(needle.len())
96        .position(|window| window == needle);
97
98    Ok(Tag {
99        data: tag_bytes,
100        sig_pos: pos,
101    })
102}
103
104pub async fn verify_tag(folder: &Path, tag: &str, keyring: &[&Path]) -> Result<()> {
105    let tag = read_tag(folder, tag).await?;
106    tag.verify(keyring).await
107}
108
109pub async fn cat_tag(folder: &Path, tag: &str) -> Result<Vec<u8>> {
110    let cmd = Command::new("git")
111        .arg("cat-file")
112        .arg("--")
113        .arg("tag")
114        .arg(tag)
115        .stdout(Stdio::piped())
116        .current_dir(folder)
117        .spawn()
118        .context("Failed to run git cat-file")?;
119
120    let out = cmd.wait_with_output().await?;
121    if !out.status.success() {
122        bail!("Process (git cat-file) exited with error: {:?}", out.status);
123    }
124
125    Ok(out.stdout)
126}
127
128pub async fn archive(path: &Path, prefix: &str, tag: &str, format: &str) -> Result<Vec<u8>> {
129    let cmd = Command::new("git")
130        .args(&["archive", "--format", format, "--prefix", prefix, "--", tag])
131        .stdout(Stdio::piped())
132        .current_dir(path)
133        .spawn()
134        .context("Failed to run git archive")?;
135
136    let out = cmd.wait_with_output().await?;
137    if !out.status.success() {
138        bail!("Process (git archive) exited with error: {:?}", out.status);
139    }
140
141    Ok(out.stdout)
142}