From 8e3c8e5c31446055d18aa87fe55def56f92e2147 Mon Sep 17 00:00:00 2001 From: Lexie Malina Date: Mon, 11 Sep 2023 19:57:31 -0500 Subject: [PATCH] WIP: container caching --- Cargo.lock | 7 +++ Cargo.toml | 1 + src/containers.rs | 132 ++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 52 +++++++++++++++++- src/utils.rs | 36 +++++++++++++ 5 files changed, 227 insertions(+), 1 deletion(-) create mode 100644 src/containers.rs diff --git a/Cargo.lock b/Cargo.lock index 04ac174..2465275 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -296,6 +296,12 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "dotenv" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" + [[package]] name = "encoding_rs" version = "0.8.32" @@ -860,6 +866,7 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" name = "podium" version = "0.1.0" dependencies = [ + "dotenv", "podman-api", "poise", "serde_path_to_error", diff --git a/Cargo.toml b/Cargo.toml index aa2f1ee..fca4e20 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,3 +10,4 @@ podman-api = "0.10" poise = "0.5.5" serde_path_to_error = "0.1.13" tokio = { version = "1.29.1", features = ["macros", "rt-multi-thread"]} +dotenv = "0.15.0" \ No newline at end of file diff --git a/src/containers.rs b/src/containers.rs new file mode 100644 index 0000000..f1b5e29 --- /dev/null +++ b/src/containers.rs @@ -0,0 +1,132 @@ +use std::collections::HashMap; +use std::ops::Deref; +use std::process; +use std::sync::{Arc, Mutex}; +use std::time::{Instant, Duration}; +use podman_api::models::{Container, MountPoint, Port, PortMapping}; +use podman_api::opts::ContainerListOpts; +use podman_api::Podman; +use crate::utils::Id; + +pub(crate) struct Containers { + cache: Arc)>>>, + connection: Podman, +} + +impl Containers { + pub(crate) fn connect(uri: &str) -> Containers { + let connection = match Podman::new(uri) { + Ok(a) => a, + Err(e) => { + eprintln!("An error has occurred: {}", e); + process::exit(32); + } + }; + let c = Containers { + cache: Arc::new(Mutex::new(None)), + connection, + }; + c + } + + fn time_since_update(&self) -> Duration { + let i = match self.cache.lock().unwrap().deref() { + Some(i) => { i.0 } + None => { return Duration::MAX; } + }; + Instant::now().duration_since(i) + } + + pub(crate) async fn sync(&self) -> Result<(), ContainerError> { + if self.time_since_update() > Duration::new(60, 0) { + return match self.sync_helper().await { + Ok(_) => { Ok(()) } + Err(e) => { Err(e) } + }; + } + Ok(()) + } + + async fn sync_helper(&self) -> Result<(), ContainerError> { + let containers_options = ContainerListOpts::builder().all(true).sync(true).build(); + + let list_containers_response = match self.connection.containers().list(&containers_options).await { + Ok(r) => { r } + Err(e) => { return Err(ContainerError::PodmanError(e)); } + }; + + let mut containers: HashMap<[u8; 32], Container> = HashMap::new(); + + for c in list_containers_response { + let id = Id::hex_id_to_byte_array(match &c.id { + Some(id) => { id } + None => { return Err(ContainerError::UnableToParseId); } + }); + + let container: Container = Container { + adjust_cpu_shares: None, + command: Some(match c.command { + None => { String::from("Unknown") } + Some(command) => { command[0].clone() } + }), + config: None, + created: match c.created { + Some(created) => { + Some(created.timestamp()) + } + None => { None } + }, + host_config: None, + id: c.id, + image: c.image, + image_id: c.image_id, + labels: c.labels, + mounts: None, + name: match &c.names { + None => { None } + Some(n) => { Some(n[0].clone()) } + }, + names: c.names, + network_settings: None, + networking_config: None, + platform: None, + ports: match c.ports { + None => { None } + Some(p) => { + let mut ports: Vec = Vec::new(); + for port_mapping in p { + let port = Port { + ip: port_mapping.host_ip, + private_port: match port_mapping.container_port { + None => { continue; } + Some(p) => { p } + }, + public_port: match port_mapping.host_port { + None => { None } + Some(p) => { Some(p) } + }, + type_: "".to_string(), + }; + ports.push(port); + } + Some(ports) + } + }, + size_root_fs: None, + size_rw: None, + state: c.state, + status: c.status, + }; + + containers.insert(id, container); + } + + Ok(()) + } +} + +#[derive(Debug)] +pub(crate) enum ContainerError { + UnableToParseId, + PodmanError(podman_api::Error), +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index a34c8f7..397bb12 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,14 @@ mod utils; +mod containers; use crate::utils::*; use podman_api::opts::ContainerListOpts; use podman_api::Podman; use poise::serenity_prelude as serenity; use std::process; +use dotenv::dotenv; use tokio; +use crate::containers::{ContainerError, Containers}; /// Display's the connected podman's version information #[poise::command(slash_command, prefix_command)] @@ -93,15 +96,61 @@ async fn system_info(ctx: Context<'_>) -> Result<(), Error> { Ok(()) } +async fn list_containers(ctx: Context<'_>) -> Result<(), Error> { + let info_response = match ctx.data().podman.info().await { + Ok(v) => v, + Err(e) => { + eprintln!("unable too get podman version."); + create_embed!(&ctx, "Error", format!("```{}```", e)); + return Ok(()); + } + }; + + let containers_options = ContainerListOpts::builder().all(true).sync(true).build(); + let containers_response = match ctx + .data() + .podman + .containers() + .list(&containers_options) + .await + { + Ok(containers_response) => containers_response, + Err(e) => { + eprintln!("unable too get podman containers."); + create_embed!(&ctx, "Error", format!("```{}```", e)); + return Ok(()); + } + }; + + let mut container_list: Vec<(String, String)> = Vec::new(); + container_list.reserve(containers_response.len()); + + + todo!() +} + #[tokio::main] async fn main() { - let podman = match Podman::new("unix:///run/podman/podman.sock") { + let api_uri = "unix:///run/podman/podman.sock"; + let podman = match Podman::new(&api_uri) { Ok(a) => a, Err(e) => { eprintln!("An error has occurred: {}", e); process::exit(32); } }; + + let containers = Containers::connect(&api_uri); + + match containers.sync().await { + Ok(_) => {} + Err(e) => { + eprintln!("{:#?}", e); + process::exit(31) + } + } + + dotenv().ok(); let framework = poise::Framework::builder() .options(poise::FrameworkOptions { commands: vec![version(), system_info()], @@ -126,6 +175,7 @@ async fn main() { .parse() .unwrap(), podman, + containers, }) }) }); diff --git a/src/utils.rs b/src/utils.rs index fba6907..351eff0 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,4 +1,6 @@ +use std::num::ParseIntError; use podman_api::Podman; +use crate::containers::Containers; pub(crate) struct EmbedData; @@ -47,12 +49,46 @@ impl EmbedData { pub(crate) struct Data { pub(crate) podman: Podman, pub(crate) podman_version: String, + pub(crate) containers: Containers, } +pub(crate) struct Id; + +impl Id { + pub(crate) fn hex_id_to_byte_array(id: &String) -> [u8; 32] { + let id_working_len: usize; + if id.len() / 2 > 32 { + id_working_len = 64; + } else { + id_working_len = (id.len() / 2) * 2; + } + + let id_bytes_vec: Result, ParseIntError> = (0..id_working_len) + .step_by(2) + .map(|i| u8::from_str_radix(&id[i..i + 2], 16)) + .collect(); + + match id_bytes_vec { + Ok(b) => { + let mut a: [u8; 32] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + for i in 0..b[..id_working_len / 2].len() { + a[i] = b[i]; + } + a + } + Err(_) => { + return [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + } + } + } +} + + // User data, which is stored and accessible in all command invocations pub(crate) type Error = Box; pub(crate) type Context<'a> = poise::Context<'a, Data, Error>; + #[macro_export] macro_rules! create_embed { ($ctx:expr, $title:expr, $($field_title:expr => $field_data:expr),*) => { -- 2.42.0