]> 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 04:25:57 +0000 (23:25 -0500)
.gitignore
Cargo.lock
Cargo.toml
src/containers/mod.rs [new file with mode: 0644]
src/main.rs
src/utils.rs

index ea8c4bf7f35f6f77f75d92ad8ce8349f6e81ddba..0b745e292507412fe437d697face3a680907a57f 100644 (file)
@@ -1 +1,2 @@
 /target
+.env
\ No newline at end of file
index 04ac17422d60143bcbd15cf9a68f6bee6b19a545..1107e94287c412d622cc73d2eb82b2c1b92d3e1d 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"
@@ -305,6 +311,15 @@ dependencies = [
  "cfg-if",
 ]
 
+[[package]]
+name = "fern"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9f0c14694cbd524c8720dd69b0e3179344f04ebb5f90f2e4a440c6ea3b2f1ee"
+dependencies = [
+ "log",
+]
+
 [[package]]
 name = "filetime"
 version = "0.2.21"
@@ -680,9 +695,9 @@ dependencies = [
 
 [[package]]
 name = "log"
-version = "0.4.19"
+version = "0.4.20"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4"
+checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
 
 [[package]]
 name = "memchr"
@@ -860,6 +875,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
 name = "podium"
 version = "0.1.0"
 dependencies = [
+ "dotenv",
+ "fern",
+ "log",
  "podman-api",
  "poise",
  "serde_path_to_error",
index aa2f1eea42f9a3127266b1326da432724e63dff4..e1ee701b50f6d8723efaadd619846152c4b27024 100644 (file)
@@ -10,3 +10,6 @@ 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"
+log = "0.4.20"
+fern = "0.6.2"
\ No newline at end of file
diff --git a/src/containers/mod.rs b/src/containers/mod.rs
new file mode 100644 (file)
index 0000000..eb8e494
--- /dev/null
@@ -0,0 +1,162 @@
+use std::collections::HashMap;
+use std::ops::Deref;
+use std::process;
+use std::sync::{Arc, Mutex};
+use std::time::{Instant, Duration};
+use log::{warn};
+use podman_api::models::{Container, Port};
+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 Err(ContainerError::UpToDate);
+        }
+        return match self.sync_helper().await {
+            Ok(_) => { Ok(()) }
+            Err(e) => { Err(e) }
+        };
+    }
+
+    pub(crate) async fn sync_now(&self) -> Result<(), ContainerError> {
+        return match self.sync_helper().await {
+            Ok(_) => { Ok(()) }
+            Err(e) => {
+                Err(e)
+            }
+        };
+    }
+
+
+    /// Helps sync cache with the podman daemon, called by [sync] or [sync_now]
+    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();
+
+        // Translate ListContainer => Model::Container
+        {
+            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);
+            }
+        }
+
+        // Scoping mutex actions
+        {
+            *match self.cache.lock() {
+                Ok(c) => { c }
+                Err(_) => {
+                    warn!("Cache was poisoned");
+                    return Err(ContainerError::UnableToLockCache);
+                }
+            } = Some((Instant::now(), containers));
+        }
+        Ok(())
+    }
+}
+
+#[derive(Debug)]
+pub(crate) enum ContainerError {
+    PodmanError(podman_api::Error),
+    UnableToLockCache,
+    UnableToParseId,
+    UpToDate,
+
+}
+
+mod tests {}
\ 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..a5a84517264d9bf883ef19192f5e51f07f577a83 100644 (file)
@@ -1,8 +1,12 @@
+use std::num::ParseIntError;
 use podman_api::Podman;
+use crate::containers::Containers;
 
+/// Helper functions for getting data for discord embeds.
 pub(crate) struct EmbedData;
 
 impl EmbedData {
+    /// Helper to grab the url of a user's avatar
     pub(crate) async fn get_author_avatar_url(ctx: &Context<'_>) -> String {
         match ctx.author_member().await {
             None => {
@@ -44,15 +48,52 @@ impl EmbedData {
     }
 }
 
+/// Data that is shared between all of the command runs.
 pub(crate) struct Data {
     pub(crate) podman: Podman,
     pub(crate) podman_version: String,
+    pub(crate) containers: Containers,
 }
 
+/// Struct for working with podman IDs
+pub(crate) struct Id;
+impl Id {
+    /// Turns the 64 char hexadecimal IDs from podman into arrays of 32 bytes for faster selection.
+    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>;
 
+/// Creates discord embed messages.
+/// I created something bad with this.
 #[macro_export]
 macro_rules! create_embed {
     ($ctx:expr, $title:expr, $($field_title:expr => $field_data:expr),*) => {