]> git.t-ch.net - podium.git/commitdiff
WIP: container caching
authorLexie Malina <alex@t-ch.net>
Tue, 12 Sep 2023 00:57:31 +0000 (19:57 -0500)
committerLexie Malina <alex@t-ch.net>
Tue, 12 Sep 2023 01:11:18 +0000 (20:11 -0500)
Cargo.lock
Cargo.toml
src/containers.rs [new file with mode: 0644]
src/main.rs
src/utils.rs

index 04ac17422d60143bcbd15cf9a68f6bee6b19a545..24652753c409c316e1c5ce508808a9a031dc2a0d 100644 (file)
@@ -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",
index aa2f1eea42f9a3127266b1326da432724e63dff4..fca4e20f20a4dcf218dbbc348b269e666e09a5af 100644 (file)
@@ -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 (file)
index 0000000..f1b5e29
--- /dev/null
@@ -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<Mutex<Option<(Instant, HashMap<[u8; 32], Container>)>>>,
+    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<Port> = 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
index a34c8f748e7f341e336be5a2d7be4f872c7e9a1d..397bb1208d15f452fcbc270678559528ecdacc22 100644 (file)
@@ -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,
                 })
             })
         });
index fba6907200621cfe6b211d2a6e01859a49858eb4..351eff0f01f0c6ef29100aa37b08be8cf4f4f62b 100644 (file)
@@ -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<Vec<u8>, 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<dyn std::error::Error + Send + Sync>;
 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),*) => {