//! PrismNET client for port management use prismnet_api::proto::{ port_service_client::PortServiceClient, subnet_service_client::SubnetServiceClient, AttachDeviceRequest, CreatePortRequest, DeletePortRequest, DetachDeviceRequest, GetPortRequest, GetSubnetRequest, }; use tonic::metadata::MetadataValue; use tonic::transport::Channel; /// PrismNET client wrapper pub struct PrismNETClient { auth_token: String, port_client: PortServiceClient, subnet_client: SubnetServiceClient, } impl PrismNETClient { /// Create a new PrismNET client pub async fn new( endpoint: String, auth_token: String, ) -> Result> { let channel = Channel::from_shared(endpoint)?.connect().await?; let port_client = PortServiceClient::new(channel.clone()); let subnet_client = SubnetServiceClient::new(channel); Ok(Self { auth_token, port_client, subnet_client, }) } fn request_with_auth( auth_token: &str, payload: T, ) -> Result, Box> { let mut request = tonic::Request::new(payload); let token_value = MetadataValue::try_from(auth_token)?; request .metadata_mut() .insert("x-photon-auth-token", token_value); Ok(request) } /// Get port details pub async fn get_port( &mut self, org_id: &str, project_id: &str, subnet_id: &str, port_id: &str, ) -> Result> { let request = Self::request_with_auth( &self.auth_token, GetPortRequest { org_id: org_id.to_string(), project_id: project_id.to_string(), subnet_id: subnet_id.to_string(), id: port_id.to_string(), }, )?; let response = self.port_client.get_port(request).await?; Ok(response .into_inner() .port .ok_or("Port not found in response")?) } /// Get subnet details, resolving by subnet ID when VPC ID is not known locally. pub async fn get_subnet( &mut self, org_id: &str, project_id: &str, subnet_id: &str, ) -> Result> { let request = Self::request_with_auth( &self.auth_token, GetSubnetRequest { org_id: org_id.to_string(), project_id: project_id.to_string(), vpc_id: String::new(), id: subnet_id.to_string(), }, )?; let response = self.subnet_client.get_subnet(request).await?; Ok(response .into_inner() .subnet .ok_or("Subnet not found in response")?) } /// Create a port for a VM-managed NIC pub async fn create_port( &mut self, org_id: &str, project_id: &str, subnet_id: &str, name: &str, description: Option<&str>, ip_address: Option<&str>, security_group_ids: Vec, ) -> Result> { let request = Self::request_with_auth( &self.auth_token, CreatePortRequest { org_id: org_id.to_string(), project_id: project_id.to_string(), subnet_id: subnet_id.to_string(), name: name.to_string(), description: description.unwrap_or_default().to_string(), ip_address: ip_address.unwrap_or_default().to_string(), security_group_ids, }, )?; let response = self.port_client.create_port(request).await?; Ok(response .into_inner() .port .ok_or("Port not found in response")?) } /// Attach a device to a port pub async fn attach_device( &mut self, org_id: &str, project_id: &str, subnet_id: &str, port_id: &str, device_id: &str, device_type: i32, ) -> Result<(), Box> { let request = Self::request_with_auth( &self.auth_token, AttachDeviceRequest { org_id: org_id.to_string(), project_id: project_id.to_string(), subnet_id: subnet_id.to_string(), port_id: port_id.to_string(), device_id: device_id.to_string(), device_type, }, )?; self.port_client.attach_device(request).await?; Ok(()) } /// Detach a device from a port pub async fn detach_device( &mut self, org_id: &str, project_id: &str, subnet_id: &str, port_id: &str, ) -> Result<(), Box> { let request = Self::request_with_auth( &self.auth_token, DetachDeviceRequest { org_id: org_id.to_string(), project_id: project_id.to_string(), subnet_id: subnet_id.to_string(), port_id: port_id.to_string(), }, )?; self.port_client.detach_device(request).await?; Ok(()) } /// Delete a port pub async fn delete_port( &mut self, org_id: &str, project_id: &str, subnet_id: &str, port_id: &str, ) -> Result<(), Box> { let request = Self::request_with_auth( &self.auth_token, DeletePortRequest { org_id: org_id.to_string(), project_id: project_id.to_string(), subnet_id: subnet_id.to_string(), id: port_id.to_string(), }, )?; self.port_client.delete_port(request).await?; Ok(()) } } #[cfg(test)] mod tests { use super::*; #[test] fn request_with_auth_adds_internal_token_header() { let request = PrismNETClient::request_with_auth( "test-token", GetPortRequest { org_id: "org".to_string(), project_id: "project".to_string(), subnet_id: "subnet".to_string(), id: "port".to_string(), }, ) .expect("request metadata should be constructible"); assert_eq!( request .metadata() .get("x-photon-auth-token") .and_then(|value| value.to_str().ok()), Some("test-token") ); } }