mod cache;
mod config_file;
mod filehash;
mod parse;
mod templates;
use crate::api::client::Client;
use crate::api::projects::Kvdb;
use crate::api::request::Validate;
use crate::api::stack;
use crate::config::deploy::DeployConfig;
use crate::envs::Envs;
use crate::error::Error;
use crate::function::Function;
use crate::secrets::Secrets;
use cache::Cache;
use config_file::ConfigFile;
use eyre::WrapErr;
use http::StatusCode;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::PathBuf;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Project {
#[serde(skip)]
pub path: PathBuf,
pub name: String,
pub url: String,
pub kvdb: Vec<Kvdb>,
pub observability: Option<Observability>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Observability {
pub dd_api_key: String,
}
impl Project {
fn new(path: PathBuf, name: String) -> Self {
Self {
path,
name,
url: String::new(),
kvdb: Vec::new(),
observability: None,
}
}
fn with_observability(mut self, dd_api_key: String) -> Self {
self.observability = Some(Observability { dd_api_key });
self
}
fn with_kvdb(mut self, kvdb: Vec<Kvdb>) -> Self {
self.kvdb = kvdb;
self
}
pub fn from_path(path: PathBuf) -> eyre::Result<Self> {
Ok(ConfigFile::from_path(path)?.try_into()?)
}
pub fn from_current_dir() -> eyre::Result<Self> {
Self::from_path(std::env::current_dir().wrap_err("Failed to get current dir")?)
}
pub async fn fetch_one(name: &str) -> eyre::Result<Self> {
let cache = Cache::new().await?;
cache
.get(name)
.wrap_err("Failed to load project information")
}
pub async fn fetch_all() -> eyre::Result<Vec<Self>> {
Cache::new()
.await
.map(|cache| cache.projects.into_values().collect())
}
pub fn clear_cache() -> eyre::Result<()> {
Cache::clear()
}
pub async fn destroy(&self) -> eyre::Result<()> {
Client::new(false)
.await
.wrap_err("Failed to create client")?
.post("/stack/destroy")
.json(&stack::destroy::Request {
project_name: self.name.to_owned(),
})
.send()
.await?;
Ok(())
}
pub async fn deploy(
&self,
functions: &[Function],
is_hotswap: bool,
deploy_config: Option<&dyn DeployConfig>,
version_message: Option<String>,
) -> eyre::Result<bool> {
let client = Client::new(deploy_config.is_some()).await?;
let secrets = Secrets::load();
if let Some(config) = deploy_config {
return config.deploy(self, secrets, functions).await;
}
let request = stack::deploy::Request {
is_hotswap,
secrets,
version_message,
functions: functions
.iter()
.map(|f| f.into())
.collect::<Vec<stack::deploy::FunctionRequest>>(),
project: self.clone(),
};
if let Some(errors) = request.validate() {
return Err(Error::new("Validation failed", Some(&errors.join("\n"))).into());
}
log::debug!(
"Sending request to deploy:\n{}",
serde_json::to_string_pretty(&request)?
);
let result = client
.post("/stack/deploy")
.json(&request)
.send()
.await
.inspect_err(|err| log::error!("Error while requesting deploy: {err:?}"))
.wrap_err(Error::new(
"Network request failed",
Some("Try again in a few seconds."),
))?;
let status = result.status();
log::info!("got status from /stack/deploy: {status}");
log::info!("got response from /stack/deploy: {}", result.text().await?);
match status {
StatusCode::OK => eyre::Ok(true),
StatusCode::NOT_MODIFIED => eyre::Ok(false),
_ => Err(Error::new(
"Deployment request failed",
Some("Try again in a few seconds."),
)
.into()),
}
}
pub async fn status(&self) -> eyre::Result<stack::status::Response> {
Self::status_by_name(&self.name).await
}
pub async fn status_by_name(name: &str) -> eyre::Result<stack::status::Response> {
let client = Client::new(false).await?;
let result = client
.post("/stack/status")
.json(&stack::status::Request {
name: name.to_owned(),
})
.send()
.await
.wrap_err(Error::new(
"Network request failed",
Some("Try again in a few seconds."),
))?;
let status = result.status();
let text = result.text().await?;
log::debug!("Got response from /stack/status:\n{status}\n{text}");
if status != StatusCode::OK {
return Err(
Error::new("Status request failed", Some("Try again in a few seconds.")).into(),
);
}
serde_json::from_str(&text).wrap_err("Failed to parse response")
}
pub fn url(&self) -> String {
self.url.to_lowercase()
}
pub fn environment(&self) -> HashMap<String, String> {
Envs::load()
}
}