139
crates/flux-releaser/src/services/archive.rs
Normal file
139
crates/flux-releaser/src/services/archive.rs
Normal file
@@ -0,0 +1,139 @@
|
||||
#[derive(Clone)]
|
||||
pub struct Archive {}
|
||||
|
||||
use std::{io::Cursor, path::Path};
|
||||
|
||||
pub mod extensions {
|
||||
|
||||
use std::{env::temp_dir, path::PathBuf};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{app::SharedApp, services::release_manager::models::ArtifactID};
|
||||
|
||||
#[mockall_double::double]
|
||||
use crate::services::file_store::FileStore;
|
||||
|
||||
#[mockall_double::double]
|
||||
use super::Archive;
|
||||
use super::ArchiveFile;
|
||||
|
||||
pub trait ArchiveExt {
|
||||
fn archive(&self) -> Archive;
|
||||
}
|
||||
|
||||
impl ArchiveExt for SharedApp {
|
||||
fn archive(&self) -> Archive {
|
||||
Archive::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait ArchiveUploadExt {
|
||||
async fn upload_archive(
|
||||
&self,
|
||||
artifact_id: ArtifactID,
|
||||
archive: ArchiveFile,
|
||||
) -> anyhow::Result<()>;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ArchiveUploadExt for FileStore {
|
||||
async fn upload_archive(
|
||||
&self,
|
||||
artifact_id: ArtifactID,
|
||||
archive: ArchiveFile,
|
||||
) -> anyhow::Result<()> {
|
||||
tracing::trace!("uploading archive: {}", artifact_id.to_string());
|
||||
let suffix = Uuid::new_v4();
|
||||
let temp_root = temp_dir();
|
||||
|
||||
let archive_path = temp_root
|
||||
.join("flux-releaser")
|
||||
.join("archives")
|
||||
.join(format!("{}-{}.tar", &artifact_id.to_string(), suffix));
|
||||
|
||||
let archive_file_guard = ArchiveFilePath(archive_path.clone());
|
||||
|
||||
tokio::fs::create_dir_all(archive_path.parent().unwrap()).await?;
|
||||
|
||||
{
|
||||
let mut archive_file = tokio::fs::File::create(&archive_path).await?;
|
||||
|
||||
archive_file.write_all(&archive.content).await?;
|
||||
archive_file.flush().await?;
|
||||
}
|
||||
|
||||
self.upload_file(artifact_id, archive_path).await?;
|
||||
|
||||
drop(archive_file_guard);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ArchiveFilePath(PathBuf);
|
||||
|
||||
// make sure we delete the archive file when we're done with it, we don't want it sticking around longer than it needs to
|
||||
impl Drop for ArchiveFilePath {
|
||||
fn drop(&mut self) {
|
||||
let file_path = self.0.clone();
|
||||
tokio::spawn(async move {
|
||||
if file_path.exists() {
|
||||
tracing::trace!("deleting archive: {}", file_path.display());
|
||||
if let Err(e) = tokio::fs::remove_file(&file_path).await {
|
||||
tracing::error!("failed to delete archive: {}", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
use mockall::{automock, mock, predicate::*};
|
||||
|
||||
use super::file_reader::{File, Files};
|
||||
|
||||
#[cfg_attr(test, automock)]
|
||||
impl Archive {
|
||||
pub fn new() -> Self {
|
||||
Self {}
|
||||
}
|
||||
|
||||
pub async fn create_archive(&self, files: Files) -> anyhow::Result<ArchiveFile> {
|
||||
tracing::trace!("archiving files");
|
||||
|
||||
let buffer = Vec::new();
|
||||
let cursor = Cursor::new(buffer);
|
||||
|
||||
let mut tar_builder = tar::Builder::new(cursor);
|
||||
|
||||
for file in files {
|
||||
let abs_file_path = file.path;
|
||||
|
||||
tracing::trace!("archiving file: {}", abs_file_path.display());
|
||||
let mut fd = std::fs::File::open(&abs_file_path)?;
|
||||
tar_builder.append_file(&abs_file_path, &mut fd)?;
|
||||
}
|
||||
|
||||
tar_builder.finish()?;
|
||||
|
||||
let cursor = tar_builder.into_inner()?;
|
||||
let buffer = cursor.into_inner();
|
||||
|
||||
Ok(buffer.into())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ArchiveFile {
|
||||
content: Vec<u8>,
|
||||
}
|
||||
|
||||
impl From<Vec<u8>> for ArchiveFile {
|
||||
fn from(value: Vec<u8>) -> Self {
|
||||
Self { content: value }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user