use std::fmt;
use rust_i18n::t;
use tabled::Tabled;
use timeweb_rs::{
apis::{configuration::Configuration, floating_ip_api},
models
};
use crate::{error::TwcError, output::OutputFormat};
fn fmt_id(v: &str) -> String {
v.to_string()
}
fn parse_zone(s: &str) -> Result<models::AvailabilityZone, TwcError> {
match s.to_lowercase().as_str() {
"spb-1" => Ok(models::AvailabilityZone::Spb1),
"spb-2" => Ok(models::AvailabilityZone::Spb2),
"spb-3" => Ok(models::AvailabilityZone::Spb3),
"spb-4" => Ok(models::AvailabilityZone::Spb4),
"msk-1" => Ok(models::AvailabilityZone::Msk1),
"nsk-1" => Ok(models::AvailabilityZone::Nsk1),
"ams-1" => Ok(models::AvailabilityZone::Ams1),
"ala-1" => Ok(models::AvailabilityZone::Ala1),
"fra-1" => Ok(models::AvailabilityZone::Fra1),
other => Err(TwcError::Api(format!(
"unknown availability zone: {other} \
(expected one of spb-1, spb-2, spb-3, spb-4, msk-1, nsk-1, ams-1, ala-1, fra-1)"
)))
}
}
fn fmt_resource_id(id: Option<&models::FloatingIpResourceId>) -> String {
match id {
Some(models::FloatingIpResourceId::Number(n)) => format!("{n}"),
Some(models::FloatingIpResourceId::String(s)) => s.clone(),
None => String::new()
}
}
#[derive(Tabled)]
struct FloatingIpRow {
#[tabled(rename = "ID")]
id: String,
#[tabled(rename = "IP")]
ip: String,
#[tabled(rename = "Zone")]
zone: String,
#[tabled(rename = "Resource")]
resource: String
}
impl fmt::Display for FloatingIpRow {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} {} {} {}", self.id, self.ip, self.zone, self.resource)
}
}
pub async fn list(config: &Configuration, format: OutputFormat) -> Result<(), TwcError> {
let resp = floating_ip_api::get_floating_ips(config).await?;
let rows: Vec<FloatingIpRow> = resp
.ips
.iter()
.map(|f| FloatingIpRow {
id: f.id.clone(),
ip: f.ip.clone().unwrap_or_default(),
zone: format!("{:?}", f.availability_zone),
resource: f.resource_type.clone().unwrap_or_default()
})
.collect();
match format {
OutputFormat::Table => {
if rows.is_empty() {
println!("{}", t!("cli.no_floating_ips"));
} else {
let table = crate::output::render_table(&rows);
println!("{table}");
}
}
OutputFormat::Json | OutputFormat::Yaml => {
let out = crate::output::serialized(format, &resp.ips)
.transpose()?
.unwrap_or_default();
println!("{out}");
}
OutputFormat::Quiet => {
for f in &resp.ips {
println!("{}\t{}", f.id, f.ip.clone().unwrap_or_default());
}
}
}
Ok(())
}
pub async fn delete(config: &Configuration, id: &str) -> Result<(), TwcError> {
floating_ip_api::delete_floating_ip(config, id).await?;
println!("{}", t!("cli.floating_ip_deleted", id => id));
Ok(())
}
pub async fn info(config: &Configuration, id: &str, format: OutputFormat) -> Result<(), TwcError> {
let resp = floating_ip_api::get_floating_ip(config, id).await?;
let f = &resp.ip;
match format {
OutputFormat::Table => {
println!("ID: {}", fmt_id(&f.id));
println!("IP: {}", f.ip.clone().unwrap_or_default());
println!("Zone: {}", f.availability_zone);
println!(
"Resource type: {}",
f.resource_type.clone().unwrap_or_default()
);
println!(
"Resource ID: {}",
fmt_resource_id(f.resource_id.as_deref())
);
}
OutputFormat::Json | OutputFormat::Yaml => {
if let Some(out) = crate::output::serialized(format, f.as_ref()) {
println!("{}", out?);
}
}
OutputFormat::Quiet => {
println!("{}\t{}", fmt_id(&f.id), f.ip.clone().unwrap_or_default());
}
}
Ok(())
}
pub async fn create(
config: &Configuration,
availability_zone: &str,
format: OutputFormat
) -> Result<(), TwcError> {
let zone = parse_zone(availability_zone)?;
let req = models::CreateFloatingIp::new(false, zone);
let resp = floating_ip_api::create_floating_ip(config, req).await?;
let f = &resp.ip;
let ip = f.ip.clone().unwrap_or_default();
match format {
OutputFormat::Table => {
println!(
"{}",
t!("cli.floating_ip_created", id => fmt_id(&f.id), ip => ip)
);
}
OutputFormat::Json | OutputFormat::Yaml => {
if let Some(out) = crate::output::serialized(format, f.as_ref()) {
println!("{}", out?);
}
}
OutputFormat::Quiet => {
println!("{}\t{}", fmt_id(&f.id), ip);
}
}
Ok(())
}
pub async fn attach(config: &Configuration, id: &str, resource_id: i32) -> Result<(), TwcError> {
let req = models::BindFloatingIp::new(
"server".to_string(),
models::BindFloatingIpResourceId::Number(f64::from(resource_id))
);
floating_ip_api::bind_floating_ip(config, id, req).await?;
println!(
"{}",
t!("cli.floating_ip_attached", id => id, resource_id => resource_id)
);
Ok(())
}
pub async fn detach(config: &Configuration, id: &str) -> Result<(), TwcError> {
floating_ip_api::unbind_floating_ip(config, id).await?;
println!("{}", t!("cli.floating_ip_detached", id => id));
Ok(())
}
pub async fn set(config: &Configuration, id: &str, comment: Option<&str>) -> Result<(), TwcError> {
let mut update = models::UpdateFloatingIp::new();
if let Some(c) = comment {
update.comment = Some(c.to_string());
}
floating_ip_api::update_floating_ip(config, id, update).await?;
println!("{}", t!("cli.floating_ip_updated", id => id));
Ok(())
}