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
113
114
115
116
117
118
119
120
121
122
123
124
125
use anyhow::Context;
use async_trait::async_trait;
use slog::{debug, Logger};
use std::path::{Path, PathBuf};

use mithril_common::logging::LoggerExtensions;
use mithril_common::StdResult;

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,

    logger: Logger,
}

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

#[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()
        );

        debug!(self.logger, "Snapshot 'uploaded' to local storage"; "location" => &location);
        Ok(location)
    }
}

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

    use crate::http_server;
    use crate::snapshot_uploaders::SnapshotUploader;
    use crate::test_tools::TestLogger;

    use super::LocalSnapshotUploader;

    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(), TestLogger::stdout());

        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(),
            TestLogger::stdout(),
        );
        uploader.upload_snapshot(&archive).await.unwrap();

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