use crate::api::stack;
use crate::commands::build::pipeline::Pipeline;
use crate::commands::deploy::DeployCommand;
use crate::config::build_config;
use crate::error::Error;
use crate::function::Function;
use crate::project::Project;
use crate::runner::Runner;
use crate::writer::Writer;
use eyre::Context;
use reqwest::StatusCode;
use serde_json::json;
use std::collections::HashMap;
use std::path::PathBuf;
pub(crate) struct DeployRunner<'a> {
pub(crate) command: DeployCommand,
pub(crate) writer: &'a Writer,
}
impl Runner for DeployRunner<'_> {
async fn run(&mut self) -> Result<(), Error> {
let project = Project::from_current_dir()?;
if project
.clone()
.observability
.filter(|o| o.dd_api_key.is_empty())
.is_some()
{
return Err(Error::new(
"DataDog API key is missing",
Some("Failed to read from specified env var. Check kinetics.toml:[observability]"),
));
}
if self.command.envs {
self.deploy_envs(project).await?;
} else {
self.deploy_all(project).await?;
}
self.writer.json(json!({"success": true}))?;
Ok(())
}
}
impl DeployRunner<'_> {
async fn deploy_envs(&mut self, project: Project) -> eyre::Result<()> {
self.writer.text(&format!(
"{}...\n",
console::style("Provisioning envs").green().bold()
))?;
let client = self.api_client().await?;
let functions: Vec<Function> = project
.parse(
PathBuf::from(build_config()?.kinetics_path),
&self.command.functions,
)?
.iter()
.filter(|f| f.is_deploying)
.cloned()
.collect();
if functions.is_empty() {
self.writer.text(&format!(
"{}\n",
console::style("No functions found").yellow().bold()
))?;
return Ok(());
}
let mut envs = HashMap::new();
for function in &functions {
let function_envs = function.environment();
let envs_string = function_envs
.keys()
.map(|k| k.as_str())
.collect::<Vec<_>>()
.join(", ");
self.writer.text(&format!(
"{} {}\n",
console::style(function.name.clone()).bold(),
if envs_string.is_empty() {
console::style("None").dim().yellow()
} else {
console::style(envs_string.as_str()).dim()
}
))?;
envs.insert(function.name.clone(), function_envs.clone());
}
let result = client
.post("/stack/deploy/envs")
.json(&stack::deploy::envs::Request {
project_name: project.name.clone(),
functions: envs,
})
.send()
.await
.wrap_err("Request to update envs failed")?;
let status = result.status();
let response_text = result.text().await?;
log::debug!("Got status from /stack/deploy/envs: {}", status);
log::debug!("Got response from /stack/deploy/envs: {}", response_text);
let response_json: stack::deploy::envs::Response = serde_json::from_str(&response_text)
.wrap_err("Failed to parse response from the backend as JSON")?;
if StatusCode::OK != status {
log::error!("Got error response: {}", response_text);
return Err(eyre::eyre!("Failed to deploy envs"));
}
let fails = response_json.fails;
if !fails.is_empty() {
return Err(eyre::eyre!(
"Failed to provision envs for: {}",
fails
.iter()
.map(|v| v.as_str())
.collect::<Vec<&str>>()
.join(", "),
));
}
self.writer
.text(&format!("{}\n", console::style("Done").green().bold()))?;
Ok(())
}
async fn deploy_all(&self, project: Project) -> eyre::Result<()> {
Pipeline::builder(self.writer)
.set_max_concurrent(self.command.max_concurrency)
.with_deploy_enabled(true)
.with_hotswap(self.command.hotswap)
.with_version_message(self.command.message.clone())
.set_project(project)
.build()
.wrap_err("Failed to build pipeline")?
.run(&self.command.functions)
.await
}
}