use crate::api::APIErrorResponse; use crate::{RoleCommands, TableStyle}; use comfy_table::modifiers::UTF8_ROUND_CORNERS; use comfy_table::presets::UTF8_FULL; use comfy_table::Table; use serde::{Deserialize, Serialize}; use std::error::Error; use std::fs; use url::Url; pub async fn role_main(command: RoleCommands, server: Url) -> Result<(), Box> { match command { RoleCommands::List { table_style } => list_roles(server, table_style).await, RoleCommands::Lookup { id } => get_role(id, server).await, RoleCommands::Create { name, description, rules_json, } => create_role(name, description, rules_json, server).await, RoleCommands::Delete { id } => delete_role(id, server).await, RoleCommands::Update { id, description, rules_json, } => update_role(id, description, rules_json, server).await, } } #[derive(Deserialize)] pub struct RoleListResp { pub data: Vec, } #[derive(Deserialize)] pub struct Role { pub id: String, pub name: String, pub description: String, #[serde(rename = "firewallRules")] pub firewall_rules: Vec, #[serde(rename = "createdAt")] pub created_at: String, #[serde(rename = "modifiedAt")] pub modified_at: String, } #[derive(Deserialize, Serialize)] pub struct RoleFirewallRule { pub protocol: String, pub description: String, #[serde(rename = "allowedRoleID")] pub allowed_role_id: Option, #[serde(rename = "portRange")] pub port_range: Option, } #[derive(Deserialize, Serialize)] pub struct RoleFirewallRulePortRange { pub from: u16, pub to: u16, } pub async fn list_roles(server: Url, table_style: TableStyle) -> Result<(), Box> { let client = reqwest::Client::new(); // load session token let sess_token_store = dirs::config_dir().unwrap().join("tfcli-session.token"); let session_token = fs::read_to_string(&sess_token_store)?; let auth_token_store = dirs::config_dir().unwrap().join("tfcli-auth.token"); let auth_token = fs::read_to_string(&auth_token_store)?; let token = format!("{} {}", session_token, auth_token); let res = client .get(server.join("/v1/roles")?) .bearer_auth(token) .send() .await?; if res.status().is_success() { let resp: RoleListResp = res.json().await?; if resp.data.is_empty() { println!("No roles found"); return Ok(()); } if matches!(table_style, TableStyle::List) { for role in &resp.data { println!(" Role: {} ({})", role.name, role.id); println!(" Description: {}", role.description); for rule in &role.firewall_rules { println!("Rule Description: {}", rule.description); println!( " Allowed Role: {}", rule.allowed_role_id .as_ref() .unwrap_or(&"All roles".to_string()) ); println!(" Protocol: {}", rule.protocol); println!( " Port Range: {}", if let Some(pr) = rule.port_range.as_ref() { format!("{}-{}", pr.from, pr.to) } else { "Any".to_string() } ); } println!(" Created: {}", role.created_at); println!(" Updated: {}", role.modified_at); } return Ok(()); } let mut table = Table::new(); match table_style { TableStyle::List => unreachable!(), TableStyle::Basic => (), TableStyle::Pretty => { table .load_preset(UTF8_FULL) .apply_modifier(UTF8_ROUND_CORNERS); } }; table.set_header(vec![ "ID", "Name", "Description", "Rule Count", "Created", "Updated", ]); for role in &resp.data { table.add_row(vec![ &role.id, &role.name, &role.description, role.firewall_rules.len().to_string().as_str(), &role.created_at, &role.modified_at, ]); } println!("{table}"); } else { let resp: APIErrorResponse = res.json().await?; eprintln!( "[error] Error listing roles: {} {}", resp.errors[0].code, resp.errors[0].message ); std::process::exit(1); } Ok(()) } #[derive(Deserialize)] pub struct RoleGetResponse { pub data: Role, } pub async fn get_role(id: String, server: Url) -> Result<(), Box> { let client = reqwest::Client::new(); // load session token let sess_token_store = dirs::config_dir().unwrap().join("tfcli-session.token"); let session_token = fs::read_to_string(&sess_token_store)?; let auth_token_store = dirs::config_dir().unwrap().join("tfcli-auth.token"); let auth_token = fs::read_to_string(&auth_token_store)?; let token = format!("{} {}", session_token, auth_token); let res = client .get(server.join(&format!("/v1/roles/{}", id))?) .bearer_auth(token) .send() .await?; if res.status().is_success() { let role: Role = res.json::().await?.data; println!(" Role: {} ({})", role.name, role.id); println!(" Description: {}", role.description); for rule in &role.firewall_rules { println!("Rule Description: {}", rule.description); println!( " Allowed Role: {}", rule.allowed_role_id .as_ref() .unwrap_or(&"All roles".to_string()) ); println!(" Protocol: {}", rule.protocol); println!( " Port Range: {}", if let Some(pr) = rule.port_range.as_ref() { format!("{}-{}", pr.from, pr.to) } else { "Any".to_string() } ); } println!(" Created: {}", role.created_at); println!(" Updated: {}", role.modified_at); } else { let resp: APIErrorResponse = res.json().await?; eprintln!( "[error] Error listing roles: {} {}", resp.errors[0].code, resp.errors[0].message ); std::process::exit(1); } Ok(()) } #[derive(Serialize)] pub struct RoleCreateBody { pub name: String, pub description: String, #[serde(rename = "firewallRules")] pub firewall_rules: Vec, } pub async fn create_role( name: String, description: String, rules_json: String, server: Url, ) -> Result<(), Box> { let client = reqwest::Client::new(); let rules: Vec = match serde_json::from_str(&rules_json) { Ok(r) => r, Err(e) => { eprintln!("[error] error parsing rulesJson: {}", e); std::process::exit(1); } }; // load session token let sess_token_store = dirs::config_dir().unwrap().join("tfcli-session.token"); let session_token = fs::read_to_string(&sess_token_store)?; let auth_token_store = dirs::config_dir().unwrap().join("tfcli-auth.token"); let auth_token = fs::read_to_string(&auth_token_store)?; let token = format!("{} {}", session_token, auth_token); let res = client .post(server.join("/v1/roles")?) .json(&RoleCreateBody { name, description, firewall_rules: rules, }) .bearer_auth(token) .send() .await?; if res.status().is_success() { let role: Role = res.json::().await?.data; println!(" Role: {} ({})", role.name, role.id); println!(" Description: {}", role.description); for rule in &role.firewall_rules { println!("Rule Description: {}", rule.description); println!( " Allowed Role: {}", rule.allowed_role_id .as_ref() .unwrap_or(&"All roles".to_string()) ); println!(" Protocol: {}", rule.protocol); println!( " Port Range: {}", if let Some(pr) = rule.port_range.as_ref() { format!("{}-{}", pr.from, pr.to) } else { "Any".to_string() } ); } println!(" Created: {}", role.created_at); println!(" Updated: {}", role.modified_at); } else { let resp: APIErrorResponse = res.json().await?; eprintln!( "[error] Error creating role: {} {}", resp.errors[0].code, resp.errors[0].message ); std::process::exit(1); } Ok(()) } #[derive(Serialize)] pub struct RoleUpdateBody { pub description: String, #[serde(rename = "firewallRules")] pub firewall_rules: Vec, } pub async fn update_role( id: String, description: String, rules_json: String, server: Url, ) -> Result<(), Box> { let client = reqwest::Client::new(); let rules: Vec = match serde_json::from_str(&rules_json) { Ok(r) => r, Err(e) => { eprintln!("[error] error parsing rulesJson: {}", e); std::process::exit(1); } }; // load session token let sess_token_store = dirs::config_dir().unwrap().join("tfcli-session.token"); let session_token = fs::read_to_string(&sess_token_store)?; let auth_token_store = dirs::config_dir().unwrap().join("tfcli-auth.token"); let auth_token = fs::read_to_string(&auth_token_store)?; let token = format!("{} {}", session_token, auth_token); let res = client .put(server.join(&format!("/v1/roles/{}", id))?) .json(&RoleUpdateBody { description, firewall_rules: rules, }) .bearer_auth(token) .send() .await?; if res.status().is_success() { let role: Role = res.json::().await?.data; println!(" Role: {} ({})", role.name, role.id); println!(" Description: {}", role.description); for rule in &role.firewall_rules { println!("Rule Description: {}", rule.description); println!( " Allowed Role: {}", rule.allowed_role_id .as_ref() .unwrap_or(&"All roles".to_string()) ); println!(" Protocol: {}", rule.protocol); println!( " Port Range: {}", if let Some(pr) = rule.port_range.as_ref() { format!("{}-{}", pr.from, pr.to) } else { "Any".to_string() } ); } println!(" Created: {}", role.created_at); println!(" Updated: {}", role.modified_at); } else { let resp: APIErrorResponse = res.json().await?; eprintln!( "[error] Error updating role: {} {}", resp.errors[0].code, resp.errors[0].message ); std::process::exit(1); } Ok(()) } pub async fn delete_role(id: String, server: Url) -> Result<(), Box> { let client = reqwest::Client::new(); // load session token let sess_token_store = dirs::config_dir().unwrap().join("tfcli-session.token"); let session_token = fs::read_to_string(&sess_token_store)?; let auth_token_store = dirs::config_dir().unwrap().join("tfcli-auth.token"); let auth_token = fs::read_to_string(&auth_token_store)?; let token = format!("{} {}", session_token, auth_token); let res = client .delete(server.join(&format!("/v1/roles/{}", id))?) .bearer_auth(token) .send() .await?; if res.status().is_success() { println!("Role removed"); } else { let resp: APIErrorResponse = res.json().await?; eprintln!( "[error] Error removing role: {} {}", resp.errors[0].code, resp.errors[0].message ); std::process::exit(1); } Ok(()) }