use std::env::temp_dir;
use std::path::{Path, PathBuf};
use anyhow::{anyhow, Context, Result};
use async_compression::tokio::write::GzipEncoder;
use hyper::Method;
use ignore::WalkBuilder;
use reqwest::multipart::{Form, Part};
use tokio::fs::File;
use tokio::io::AsyncWriteExt;
use tokio_tar::Builder as TarBuilder;
use super::types::{Build, SingleBuild};
use crate::commands::deploy::HOP_BUILD_BASE_URL;
use crate::state::http::HttpClient;
use crate::store::hopfile::VALID_HOP_FILENAMES;
pub async fn builder_post(http: &HttpClient, deployment_id: &str, bytes: Vec<u8>) -> Result<Build> {
let multipart = Form::new().part(
"file",
Part::bytes(bytes)
.file_name("deployment.tar.gz")
.mime_str("application/x-gzip")?,
);
let builder_uri =
std::env::var("BUILDER_URL").unwrap_or_else(|_| HOP_BUILD_BASE_URL.to_string());
let response = http
.client
.request(
Method::POST,
format!("{builder_uri}/deployments/{deployment_id}/builds",).as_str(),
)
.header("content_type", "multipart/form-data".to_string())
.multipart(multipart)
.send()
.await?;
let build = http
.handle_response::<SingleBuild>(response)
.await?
.ok_or_else(|| anyhow!("Could not create build"))?
.build;
Ok(build)
}
static DEFAULT_IGNORE_LIST: &[&str] = &[
".git",
".github",
".gitmodules",
".DS_Store",
".idea",
".vscode",
];
pub async fn compress(id: &str, base_dir: PathBuf) -> Result<String> {
let base_folder_name = Path::new(&id);
let archive_path = temp_dir().join(format!("hop_{id}.tar.gz"));
let writer = File::create(archive_path.clone()).await?;
let writer = GzipEncoder::new(writer);
let mut archive = TarBuilder::new(writer);
archive.follow_symlinks(true);
log::info!("Finding files to compress...");
let mut walker = WalkBuilder::new(base_dir.clone());
walker.add_ignore(create_global_ignore_file().await?);
walker.add_custom_ignore_filename(".hopignore");
walker.hidden(false).follow_links(true);
let walker = walker.build();
for entry in walker {
match entry {
Ok(entry) => {
log::debug!("Adding {} to tarball", entry.path().display());
if VALID_HOP_FILENAMES.contains(&entry.file_name().to_str().unwrap()) {
continue;
}
let path = entry.path().strip_prefix(&base_dir).unwrap().to_owned();
archive
.append_path_with_name(entry.path(), &(*base_folder_name).join(&path))
.await?;
}
Err(err) => {
log::warn!("Error walking: {}", err);
}
}
}
let mut buff = archive.into_inner().await?;
buff.shutdown().await?;
let mut buff = buff.into_inner();
buff.shutdown().await?;
Ok(archive_path.to_str().unwrap().into())
}
async fn create_global_ignore_file() -> Result<PathBuf> {
let path = temp_dir().join(".hopignore");
let mut file = File::create(path.clone())
.await
.context("Could not create global ignore file")?;
file.write_all(DEFAULT_IGNORE_LIST.join("\n").as_bytes())
.await?;
file.shutdown().await?;
Ok(path)
}