/target
+.env
\ No newline at end of file
"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"
"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"
[[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"
name = "podium"
version = "0.1.0"
dependencies = [
+ "dotenv",
+ "fern",
+ "log",
"podman-api",
"poise",
"serde_path_to_error",
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
--- /dev/null
+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
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)]
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()],
.parse()
.unwrap(),
podman,
+ containers,
})
})
});
+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 => {
}
}
+/// 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),*) => {