Skip to content

Commit

Permalink
feat: add inspect deployment command
Browse files Browse the repository at this point in the history
  • Loading branch information
pxseu committed May 23, 2023
1 parent 9c51c44 commit 80e0381
Show file tree
Hide file tree
Showing 6 changed files with 215 additions and 32 deletions.
115 changes: 115 additions & 0 deletions src/commands/ignite/inspect.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
use std::io::Write;

use anyhow::{ensure, Result};
use clap::Parser;
use tabwriter::TabWriter;

use crate::commands::ignite::utils::{format_deployments, get_all_deployments, get_storage};
use crate::state::State;

use super::utils::get_tiers;

#[derive(Debug, Parser)]
#[clap(about = "Inspect a deployment")]
pub struct Options {
#[clap(help = "The ID or name of the deployment")]
pub deployment: Option<String>,
}

pub async fn handle(options: Options, state: State) -> Result<()> {
let mut deployment = if let Some(id_or_name) = options.deployment {
state.get_deployment_by_name_or_id(&id_or_name).await?
} else {
let project_id = state.ctx.current_project_error()?.id;

let deployments = get_all_deployments(&state.http, &project_id).await?;
ensure!(!deployments.is_empty(), "No deployments found");
let deployments_fmt = format_deployments(&deployments, false);

let idx = dialoguer::Select::new()
.with_prompt("Select a deployment")
.items(&deployments_fmt)
.default(0)
.interact()?;

deployments[idx].clone()
};

let (tiers, storage) = tokio::join!(
get_tiers(&state.http),
get_storage(&state.http, &deployment.id)
);
let (tiers, storage) = (tiers?, storage?);

let mut tw = TabWriter::new(vec![]);

writeln!(tw, "{} ({})", deployment.name, deployment.id)?;
writeln!(tw, " Metadata")?;
writeln!(tw, "\tImage: {}", deployment.config.image.name)?;
writeln!(tw, "\tCreated: {}", deployment.created_at)?;
writeln!(
tw,
"\tContainers: {}/{}",
deployment.container_count, deployment.target_container_count
)?;
writeln!(
tw,
"\tRestart Policy: {}",
deployment.config.restart_policy.take().unwrap_or_default()
)?;
writeln!(
tw,
"\tUses ephemeral containers: {}",
if deployment.is_ephemeral() {
"Yes"
} else {
"No"
}
)?;
writeln!(
tw,
"\tEntrypoint: {}",
deployment
.config
.entrypoint
.map(|s| serde_json::to_string(&s).unwrap())
.unwrap_or_else(|| "None".to_string())
)?;
writeln!(
tw,
"\tCommand: {}",
deployment
.config
.cmd
.map(|s| serde_json::to_string(&s).unwrap())
.unwrap_or_else(|| "None".to_string())
)?;
writeln!(tw, " Resources")?;
writeln!(
tw,
"\tTier: {}",
deployment.config.resources.get_tier_name(&tiers)?
)?;
writeln!(
tw,
"\tVolume: {}",
storage
.volume
.map(|s| s.to_string())
.unwrap_or_else(|| "None".to_string())
)?;
writeln!(
tw,
"\tBuild Cache: {}",
storage
.build_cache
.map(|s| s.to_string())
.unwrap_or_else(|| "None".to_string())
)?;

tw.flush()?;

print!("{}", String::from_utf8(tw.into_inner()?)?);

Ok(())
}
6 changes: 5 additions & 1 deletion src/commands/ignite/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ mod delete;
pub mod from_compose;
mod get_env;
mod health;
mod inspect;
mod list;
mod promote;
pub mod rollout;
Expand All @@ -26,9 +27,11 @@ pub enum Commands {
List(list::Options),
#[clap(name = "rm", alias = "delete")]
Delete(delete::Options),
Update(update::Options),
#[clap(alias = "info")]
Inspect(inspect::Options),
#[clap(alias = "rollouts")]
Rollout(rollout::Options),
Update(update::Options),
Scale(scale::Options),
#[clap(name = "get-env")]
GetEnv(get_env::Options),
Expand Down Expand Up @@ -63,6 +66,7 @@ pub async fn handle(options: Options, state: State) -> Result<()> {
Commands::Create(options) => create::handle(options, state).await,
Commands::Delete(options) => delete::handle(options, state).await,
Commands::Update(options) => update::handle(options, state).await,
Commands::Inspect(options) => inspect::handle(options, state).await,
Commands::Rollout(options) => rollout::handle(options, state).await,
Commands::Scale(options) => scale::handle(options, state).await,
Commands::GetEnv(options) => get_env::handle(options, state).await,
Expand Down
57 changes: 57 additions & 0 deletions src/commands/ignite/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use serde::{Deserialize, Serialize};

use crate::commands::containers::types::ContainerType;
use crate::utils::parse_key_val;
use crate::utils::size::{parse_size, unit_multiplier};

#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
pub struct Vgpu {
Expand Down Expand Up @@ -82,6 +83,21 @@ pub struct Resources {
pub vgpu: Vec<Vgpu>,
}

impl Resources {
pub fn get_tier_name(&self, tiers: &[Tier]) -> Result<String> {
for tier in tiers {
if tier.resources.cpu == self.vcpu && tier.resources.memory == parse_size(&self.ram)? {
return Ok(format!(
"{} - {}vcpu {}B",
tier.name, tier.resources.cpu, tier.resources.memory
));
}
}

Ok(format!("{}vcpu {}", self.vcpu, self.ram))
}
}

impl Default for Resources {
fn default() -> Self {
Resources {
Expand Down Expand Up @@ -420,3 +436,44 @@ pub enum RolloutState {
Finished,
Failed,
}

#[derive(Debug, Deserialize, Clone)]
pub struct Storage {
pub volume: Option<StorageUsage>,
pub build_cache: Option<StorageUsage>,
}

#[derive(Debug, Deserialize, Clone)]
pub struct StorageUsage {
pub provisioned_size: u64,
pub used_size: u64,
}

impl Display for StorageUsage {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{} / {}",
get_size(self.used_size),
get_size(self.provisioned_size)
)
}
}

/// Get the size in human readable format
/// size is in megabytes
/// e.g. 1024 -> 1GB
/// 512 -> 512MB
/// 1 -> 1MB
fn get_size(size: u64) -> String {
// really silly but matches dont like simple expressions
const LESS_THAN_KB: u64 = unit_multiplier::KB - 1;

match size {
1..=LESS_THAN_KB => format!("{}MB", size),

_ => {
format!("{:.2}GB", size as f64 / unit_multiplier::KB as f64)
}
}
}
21 changes: 17 additions & 4 deletions src/commands/ignite/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use tokio::fs;

use super::types::{
CreateDeployment, Deployment, MultipleDeployments, Premade, Premades, RolloutEvent,
ScaleRequest, SingleDeployment, Tier, Tiers,
ScaleRequest, SingleDeployment, Storage, Tier, Tiers,
};
use crate::commands::containers::types::{ContainerOptions, ContainerType};
use crate::commands::ignite::create::Options;
Expand All @@ -21,7 +21,7 @@ use crate::commands::ignite::types::{
use crate::commands::projects::types::{Project, Sku};
use crate::commands::projects::utils::{get_quotas, get_skus};
use crate::state::http::HttpClient;
use crate::utils::size::{parse_size, UnitMultiplier};
use crate::utils::size::{parse_size, unit_multiplier};
use crate::utils::{ask_question_iter, parse_key_val};

pub const WEB_IGNITE_URL: &str = "https://console.hop.io/ignite";
Expand Down Expand Up @@ -881,13 +881,13 @@ pub fn get_price_estimate(

// per 100 MB
"ignite_ram_per_min" => {
price *= (parse_size(&resources.ram)? / (100 * UnitMultiplier::MB as u64)) as f64;
price *= (parse_size(&resources.ram)? / (100 * unit_multiplier::MB)) as f64;
}

// per 100 MB
"ignite_volume_per_min" => {
if let Some(size) = &volume {
price *= (parse_size(size)? / (100 * UnitMultiplier::MB as u64)) as f64;
price *= (parse_size(size)? / (100 * unit_multiplier::MB)) as f64;
}
}

Expand All @@ -902,6 +902,19 @@ pub fn get_price_estimate(
Ok(format!("{total:.2}"))
}

pub async fn get_storage(http: &HttpClient, deployment_id: &str) -> Result<Storage> {
let data = http
.request::<Storage>(
"GET",
&format!("/ignite/deployments/{deployment_id}/storage",),
None,
)
.await?
.ok_or_else(|| anyhow!("Failed to parse response"))?;

Ok(data)
}

#[cfg(test)]
mod test {
use super::*;
Expand Down
14 changes: 7 additions & 7 deletions src/commands/projects/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize};

use crate::{
commands::ignite::types::{Resources, Volume},
utils::size::{parse_size, UnitMultiplier},
utils::size::{parse_size, unit_multiplier},
};

// types for the API response
Expand Down Expand Up @@ -77,28 +77,28 @@ impl Quotas {
self.overrides.ram
} else {
self.default.ram
}) * UnitMultiplier::MB as u64,
}) * unit_multiplier::MB,
volume: (if self.overrides.volume > 0 {
self.overrides.volume
} else {
self.default.volume
}) * UnitMultiplier::MB as u64,
}) * unit_multiplier::MB,
}
}

pub fn usage_quota(&self) -> Quota {
Quota {
vcpu: self.usage.vcpu,
ram: self.usage.ram * UnitMultiplier::MB as u64,
volume: self.usage.volume * UnitMultiplier::MB as u64,
ram: self.usage.ram * unit_multiplier::MB,
volume: self.usage.volume * unit_multiplier::MB,
}
}

pub fn free_quota(&self) -> Quota {
Quota {
vcpu: self.default.vcpu,
ram: self.default.ram * UnitMultiplier::MB as u64,
volume: self.default.volume * UnitMultiplier::MB as u64,
ram: self.default.ram * unit_multiplier::MB,
volume: self.default.volume * unit_multiplier::MB,
}
}

Expand Down
34 changes: 14 additions & 20 deletions src/utils/size.rs
Original file line number Diff line number Diff line change
@@ -1,29 +1,23 @@
use std::str::FromStr;

use anyhow::{anyhow, Result};

// bytes have to be last because all other end with it
pub const BYTE_UNITS: [&str; 4] = ["GB", "MB", "KB", "B"];

#[derive(Debug)]
pub enum UnitMultiplier {
B = 1,
KB = 1024,
MB = 1024 * 1024,
GB = 1024 * 1024 * 1024,
}

impl FromStr for UnitMultiplier {
type Err = anyhow::Error;
pub mod unit_multiplier {
use anyhow::{bail, Result};

fn from_str(u: &str) -> Result<Self, Self::Err> {
match u.to_uppercase().as_str() {
"B" => Ok(UnitMultiplier::B),
"KB" => Ok(UnitMultiplier::KB),
"MB" => Ok(UnitMultiplier::MB),
"GB" => Ok(UnitMultiplier::GB),
pub const B: u64 = 1;
pub const KB: u64 = 1024;
pub const MB: u64 = 1024 * 1024;
pub const GB: u64 = 1024 * 1024 * 1024;

_ => Err(anyhow!("Invalid unit: {u}")),
pub fn from_str(unit: &str) -> Result<u64> {
match unit {
"GB" => Ok(GB),
"MB" => Ok(MB),
"KB" => Ok(KB),
"B" => Ok(B),
_ => bail!("Invalid unit: {unit}"),
}
}
}
Expand All @@ -43,7 +37,7 @@ pub fn parse_size(size: &str) -> Result<u64> {
return Err(anyhow!("Invalid size: {size}"));
};

Ok(size * UnitMultiplier::from_str(unit)? as u64)
Ok(size * unit_multiplier::from_str(unit)?)
}

// pub fn is_valid_mem_size(n: u64, min: u64, max: u64) -> bool {
Expand Down

0 comments on commit 80e0381

Please sign in to comment.