use chrono::{DateTime, Utc};
use serde::Serialize;
use crate::commands::request::server_request;
use nebulous::config::GlobalConfig;
use nebulous::resources::v1::containers::models::{V1Container, V1Containers};
use serde_json::Value;
use std::error::Error;
use tracing::debug;
pub async fn get_containers(id: Option<String>) -> Result<(), Box<dyn Error>> {
let containers: Vec<V1Container> = match id {
Some(id) => {
let url = format!("/v1/containers/{}", id);
let response = server_request(url.as_str(), reqwest::Method::GET).await?;
let container: V1Container = response.json().await?;
let mut buf = Vec::new();
{
let mut serializer = serde_yaml::Serializer::new(&mut buf);
container.serialize(&mut serializer)?;
}
let yaml = String::from_utf8(buf)?;
println!("{}", yaml);
return Ok(());
}
None => {
let response = server_request("/v1/containers", reqwest::Method::GET).await?;
let container_list: V1Containers = response.json().await?;
container_list.containers
}
};
let mut table = prettytable::Table::new();
table.add_row(prettytable::Row::new(vec![
prettytable::Cell::new("ID"),
prettytable::Cell::new("NAME"),
prettytable::Cell::new("NAMESPACE"),
prettytable::Cell::new("IMAGE"),
prettytable::Cell::new("PLATFORM"),
prettytable::Cell::new("RESTART"),
prettytable::Cell::new("STATUS"),
prettytable::Cell::new("ACCELERATOR"),
prettytable::Cell::new("PRICE"),
prettytable::Cell::new("TAILNET URL"),
prettytable::Cell::new("UPTIME"),
]));
for container in containers {
let id = container.metadata.id;
let name = container.metadata.name;
let namespace = container.metadata.namespace;
let image = container.image;
let platform = container.platform;
let restart = container.restart;
let status = match container.status.clone() {
Some(status) => status.status.unwrap_or("N/A".to_string()),
None => "N/A".to_string(),
};
let accelerator = match container.status.clone() {
Some(status) => status.accelerator.unwrap_or("N/A".to_string()),
None => "N/A".to_string(),
};
let tailnet_url = match container.status.clone() {
Some(status) => status.tailnet_url.unwrap_or("N/A".to_string()),
None => "N/A".to_string(),
};
let price_str = match container.status.clone() {
Some(status) => status
.cost_per_hr
.map(|cost| format!("{:.2}", cost))
.unwrap_or("N/A".to_string()),
None => "N/A".to_string(),
};
let uptime = {
let dt = DateTime::<Utc>::from_timestamp(container.metadata.created_at, 0)
.unwrap_or_default();
let duration = Utc::now().signed_duration_since(dt);
if duration.num_days().abs() > 0 {
format!("{}d", duration.num_days())
} else if duration.num_hours().abs() > 0 {
format!("{}hr", duration.num_hours())
} else if duration.num_minutes().abs() > 0 {
format!("{}m", duration.num_minutes())
} else {
format!("{}s", duration.num_seconds())
}
};
table.add_row(prettytable::Row::new(vec![
prettytable::Cell::new(&id),
prettytable::Cell::new(&name),
prettytable::Cell::new(&namespace),
prettytable::Cell::new(&image),
prettytable::Cell::new(&platform),
prettytable::Cell::new(&restart),
prettytable::Cell::new(&status),
prettytable::Cell::new(&accelerator),
prettytable::Cell::new(&price_str),
prettytable::Cell::new(&tailnet_url),
prettytable::Cell::new(&uptime),
]));
}
table.set_format(*prettytable::format::consts::FORMAT_CLEAN);
table.printstd();
Ok(())
}
pub async fn get_secrets(id: Option<String>) -> Result<(), Box<dyn Error>> {
let secrets = match id {
Some(id) => {
let url = format!("/v1/secrets/{}", id);
let response = server_request(url.as_str(), reqwest::Method::GET).await?;
let secret: Value = response.json().await?;
vec![secret]
}
None => {
let response = server_request("/v1/secrets", reqwest::Method::GET).await?;
let secrets: Value = response.json().await?;
secrets.as_array().unwrap_or(&Vec::new()).to_vec()
}
};
let mut table = prettytable::Table::new();
table.add_row(prettytable::Row::new(vec![
prettytable::Cell::new("ID"),
prettytable::Cell::new("NAME"),
prettytable::Cell::new("NAMESPACE"),
prettytable::Cell::new("CREATED"),
prettytable::Cell::new("UPDATED"),
]));
for secret in secrets {
if let Value::Object(secret_obj) = secret {
let metadata = secret_obj.get("metadata").and_then(Value::as_object);
let id = metadata
.and_then(|m| m.get("id"))
.and_then(Value::as_str)
.unwrap_or("N/A");
let name = metadata
.and_then(|m| m.get("name"))
.and_then(Value::as_str)
.unwrap_or("N/A");
let namespace = metadata
.and_then(|m| m.get("namespace"))
.and_then(Value::as_str)
.unwrap_or("N/A");
let created = secret_obj
.get("metadata")
.and_then(Value::as_object)
.and_then(|m| m.get("created_at"))
.and_then(Value::as_i64)
.map(|timestamp| {
DateTime::<Utc>::from_timestamp(timestamp, 0)
.unwrap_or_default()
.format("%Y-%m-%d %H:%M:%S")
.to_string()
})
.unwrap_or("N/A".to_string());
let updated = secret_obj
.get("metadata")
.and_then(Value::as_object)
.and_then(|m| m.get("updated_at"))
.and_then(Value::as_i64)
.map(|timestamp| {
DateTime::<Utc>::from_timestamp(timestamp, 0)
.unwrap_or_default()
.format("%Y-%m-%d %H:%M:%S")
.to_string()
})
.unwrap_or("N/A".to_string());
table.add_row(prettytable::Row::new(vec![
prettytable::Cell::new(id),
prettytable::Cell::new(name),
prettytable::Cell::new(namespace),
prettytable::Cell::new(&created),
prettytable::Cell::new(&updated),
]));
}
}
table.set_format(*prettytable::format::consts::FORMAT_CLEAN);
table.printstd();
Ok(())
}
pub async fn get_accelerators(platform: Option<String>) -> Result<(), Box<dyn Error>> {
use nebulous::accelerator::aws::AwsProvider;
use nebulous::accelerator::base::{AcceleratorProvider, Config};
use nebulous::accelerator::runpod::RunPodProvider;
use prettytable::{format, Cell, Row, Table};
let config = Config::default();
let mut table = Table::new();
let mut headers = vec![Cell::new("NAME"), Cell::new("MEMORY (GB)")];
if platform.is_some() {
headers.push(Cell::new("PLATFORM NAME"));
}
table.add_row(Row::new(headers));
let provider: Option<Box<dyn AcceleratorProvider>> = match platform.as_deref() {
Some("aws") => Some(Box::new(AwsProvider::new())),
Some("runpod") => Some(Box::new(RunPodProvider::new())),
Some(unknown) => {
eprintln!(
"Unknown platform: {}. Supported platforms are 'aws' and 'runpod'.",
unknown
);
return Ok(());
}
None => None,
};
for acc in &config.accelerators.supported {
let mut row = vec![Cell::new(&acc.name), Cell::new(&acc.memory.to_string())];
if let Some(ref provider) = provider {
let platform_name = provider
.get_platform_name(&acc.name)
.map(|s| s.as_str())
.unwrap_or("N/A");
row.push(Cell::new(platform_name));
}
table.add_row(Row::new(row));
}
table.set_format(*format::consts::FORMAT_CLEAN);
table.printstd();
Ok(())
}
pub async fn get_processors(
name: Option<String>,
namespace: Option<String>,
) -> Result<(), Box<dyn Error>> {
let config = GlobalConfig::read()?;
debug!("Config: {:?}", config);
let current_server = config.get_current_server_config().unwrap();
let server = current_server.server.as_ref().unwrap();
let api_key = current_server.api_key.as_ref().unwrap();
let bearer_token = format!("Bearer {}", api_key);
let client = reqwest::Client::new();
if let (Some(proc_name), Some(proc_namespace)) = (name, namespace) {
let url = format!("{}/v1/processors/{}/{}", server, proc_namespace, proc_name);
let request = client.get(&url).header("Authorization", &bearer_token);
let response = request.send().await?;
if !response.status().is_success() {
return Err(format!("Failed to get processor: {}", response.status()).into());
}
let mut processor: Value = response.json().await?;
remove_null_values(&mut processor);
let mut buf = Vec::new();
{
let mut serializer = serde_yaml::Serializer::new(&mut buf);
processor.serialize(&mut serializer)?;
}
let yaml = String::from_utf8(buf)?;
println!("{}", yaml);
return Ok(());
}
let url = format!("{}/v1/processors", server);
let request = client.get(&url).header("Authorization", &bearer_token);
let response = request.send().await?;
if !response.status().is_success() {
return Err(format!("Failed to get processors: {}", response.status()).into());
}
let mut processors_response: Value = response.json().await?;
remove_null_values(&mut processors_response);
let mut table = prettytable::Table::new();
table.add_row(prettytable::Row::new(vec![
prettytable::Cell::new("ID"),
prettytable::Cell::new("NAME"),
prettytable::Cell::new("NAMESPACE"),
prettytable::Cell::new("STREAM"),
prettytable::Cell::new("REPLICAS (MIN/MAX)"),
prettytable::Cell::new("STATUS"),
prettytable::Cell::new("PRESSURE"),
prettytable::Cell::new("CREATED"),
prettytable::Cell::new("UPDATED"),
]));
let empty_vec = Vec::new();
let processor_list = processors_response
.get("processors")
.and_then(Value::as_array)
.unwrap_or(&empty_vec);
for processor in processor_list {
if let Value::Object(proc_obj) = processor {
debug!("Processor: {:?}", proc_obj);
let metadata = proc_obj.get("metadata").and_then(Value::as_object);
let status_obj = proc_obj.get("status").and_then(Value::as_object);
let id = metadata
.and_then(|m| m.get("id"))
.and_then(Value::as_str)
.unwrap_or("N/A");
let name = metadata
.and_then(|m| m.get("name"))
.and_then(Value::as_str)
.unwrap_or("N/A");
let namespace = metadata
.and_then(|m| m.get("namespace"))
.and_then(Value::as_str)
.unwrap_or("N/A");
let stream = proc_obj
.get("stream")
.and_then(Value::as_str)
.unwrap_or("N/A");
let min_replicas_val = proc_obj.get("min_replicas").and_then(Value::as_i64);
let max_replicas_val = proc_obj.get("max_replicas").and_then(Value::as_i64);
let replicas_str = match (min_replicas_val, max_replicas_val) {
(Some(min), Some(max)) => format!("{}/{}", min, max),
(Some(min), None) => format!("{}/N/A", min),
(None, Some(max)) => format!("N/A/{}", max),
(None, None) => "N/A".to_string(),
};
let status = status_obj
.and_then(|s| s.get("status"))
.and_then(Value::as_str)
.unwrap_or("N/A");
let pressure = status_obj
.and_then(|s| s.get("pressure"))
.and_then(Value::as_i64)
.map(|p| p.to_string())
.unwrap_or_else(|| "N/A".to_string());
let created = metadata
.and_then(|m| m.get("created_at"))
.and_then(Value::as_i64)
.map(|timestamp| {
DateTime::<Utc>::from_timestamp(timestamp, 0)
.unwrap_or_default()
.format("%Y-%m-%d %H:%M:%S")
.to_string()
})
.unwrap_or_else(|| "N/A".to_string());
let updated = metadata
.and_then(|m| m.get("updated_at"))
.and_then(Value::as_i64)
.map(|timestamp| {
DateTime::<Utc>::from_timestamp(timestamp, 0)
.unwrap_or_default()
.format("%Y-%m-%d %H:%M:%S")
.to_string()
})
.unwrap_or_else(|| "N/A".to_string());
table.add_row(prettytable::Row::new(vec![
prettytable::Cell::new(id),
prettytable::Cell::new(name),
prettytable::Cell::new(namespace),
prettytable::Cell::new(stream),
prettytable::Cell::new(&replicas_str),
prettytable::Cell::new(status),
prettytable::Cell::new(&pressure),
prettytable::Cell::new(&created),
prettytable::Cell::new(&updated),
]));
}
}
table.set_format(*prettytable::format::consts::FORMAT_CLEAN);
table.printstd();
Ok(())
}
fn remove_null_values(value: &mut Value) {
match value {
Value::Object(map) => {
let keys_with_nulls: Vec<_> = map
.iter()
.filter_map(|(k, v)| if v.is_null() { Some(k.clone()) } else { None })
.collect();
for k in keys_with_nulls {
map.remove(&k);
}
for v in map.values_mut() {
remove_null_values(v);
}
}
Value::Array(arr) => {
for v in arr.iter_mut() {
remove_null_values(v);
}
}
_ => {}
}
}
pub async fn get_platforms() -> Result<(), Box<dyn Error>> {
let platforms = vec!["gce", "runpod", "ec2"];
println!("{:?}", platforms);
Ok(())
}