1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
use anyhow::Context;
use async_trait::async_trait;
use mithril_common::StdResult;
use slog_scope::debug;
use std::path::{Path, PathBuf};

use crate::http_server;
use crate::snapshot_uploaders::{SnapshotLocation, SnapshotUploader};
use crate::tools;

/// LocalSnapshotUploader is a snapshot uploader working using local files
pub struct LocalSnapshotUploader {
    /// Snapshot server listening IP
    snapshot_server_url: String,

    /// Target folder where to store snapshots archive
    target_location: PathBuf,
}

impl LocalSnapshotUploader {
    /// LocalSnapshotUploader factory
    pub(crate) fn new(snapshot_server_url: String, target_location: &Path) -> Self {
        debug!("New LocalSnapshotUploader created"; "snapshot_server_url" => &snapshot_server_url);
        Self {
            snapshot_server_url,
            target_location: target_location.to_path_buf(),
        }
    }
}

#[async_trait]
impl SnapshotUploader for LocalSnapshotUploader {
    async fn upload_snapshot(&self, snapshot_filepath: &Path) -> StdResult<SnapshotLocation> {
        let archive_name = snapshot_filepath.file_name().unwrap().to_str().unwrap();
        let target_path = &self.target_location.join(archive_name);
        tokio::fs::copy(snapshot_filepath, target_path)
            .await
            .with_context(|| "Snapshot copy failure")?;

        let digest = tools::extract_digest_from_path(Path::new(archive_name));
        let location = format!(
            "{}{}/artifact/snapshot/{}/download",
            self.snapshot_server_url,
            http_server::SERVER_BASE_PATH,
            digest.unwrap()
        );

        Ok(location)
    }
}

#[cfg(test)]
mod tests {
    use super::LocalSnapshotUploader;
    use crate::http_server;
    use crate::snapshot_uploaders::SnapshotUploader;
    use std::fs::File;
    use std::io::Write;
    use std::path::{Path, PathBuf};
    use tempfile::tempdir;

    fn create_fake_archive(dir: &Path, digest: &str) -> PathBuf {
        let file_path = dir.join(format!("test.{digest}.tar.gz"));
        let mut file = File::create(&file_path).unwrap();
        writeln!(
            file,
            "I swear, this is an archive, not a temporary test file."
        )
        .unwrap();

        file_path
    }

    #[tokio::test]
    async fn should_extract_digest_to_deduce_location() {
        let source_dir = tempdir().unwrap();
        let target_dir = tempdir().unwrap();
        let url = "http://test.com:8080/".to_string();
        let digest = "41e27b9ed5a32531b95b2b7ff3c0757591a06a337efaf19a524a998e348028e7";
        let archive = create_fake_archive(source_dir.path(), digest);
        let expected_location = format!(
            "{}{}/artifact/snapshot/{}/download",
            url,
            http_server::SERVER_BASE_PATH,
            &digest
        );
        let uploader = LocalSnapshotUploader::new(url, target_dir.path());

        let location = uploader
            .upload_snapshot(&archive)
            .await
            .expect("local upload should not fail");

        assert_eq!(expected_location, location);
    }

    #[tokio::test]
    async fn should_copy_file_to_target_location() {
        let source_dir = tempdir().unwrap();
        let target_dir = tempdir().unwrap();
        let digest = "41e27b9ed5a32531b95b2b7ff3c0757591a06a337efaf19a524a998e348028e7";
        let archive = create_fake_archive(source_dir.path(), digest);
        let uploader =
            LocalSnapshotUploader::new("http://test.com:8080/".to_string(), target_dir.path());
        uploader.upload_snapshot(&archive).await.unwrap();

        assert!(target_dir
            .path()
            .join(archive.file_name().unwrap())
            .exists());
    }
}