diff --git a/src/get.rs b/src/get.rs index 301af95..6faac57 100644 --- a/src/get.rs +++ b/src/get.rs @@ -5,6 +5,7 @@ mod cronjob; mod pod; mod lister; mod daemonset; +mod secret; use std::error::Error; @@ -28,7 +29,7 @@ use crate::get::ingress::IngresssLister; use crate::get::lister::Lister; use crate::get::node::NodeLister; use crate::get::pod::PodLister; - +use crate::get::secret::SecretLister; #[derive(Debug, Clone, Args)] @@ -76,11 +77,11 @@ pub async fn get(args: GetArgs) -> Result<(), Box> { match args.commands { GetCommands::Pod(args) => get_pod(global_args, args).await, GetCommands::Deployment(args) => get_deployment(global_args, args).await, - GetCommands::Daemonset(args) => todo!(), + GetCommands::Daemonset(args) => get_daemonsets(global_args, args).await, GetCommands::Node(args) => get_nodes(global_args, args).await, GetCommands::Ingress(args) => get_ingress(global_args, args).await, GetCommands::Cronjob(args) => get_cronjobs(global_args, args).await, - GetCommands::Secret(args) => todo!(), + GetCommands::Secret(args) => get_secrets(global_args, args).await, } } @@ -146,3 +147,9 @@ async fn get_nodes(global_args: GetArgs, args: GetObjectArgs) -> Result<(), Box< let lister = NodeLister {}; get_objects(global_args, args, &lister).await } + +async fn get_secrets(global_args: GetArgs, args: GetObjectArgs) -> Result<(), Box> { + let lister = SecretLister{}; + get_objects(global_args, args, &lister).await +} + diff --git a/src/get/daemonset.rs b/src/get/daemonset.rs index bd5deb8..b262c36 100644 --- a/src/get/daemonset.rs +++ b/src/get/daemonset.rs @@ -4,6 +4,7 @@ use itertools::Itertools; use crate::get::{GetObjectArgs, IdCommand, Lister}; use crate::skatelet::{PodmanPodInfo, PodmanPodStatus, SystemInfo}; use crate::state::state::ClusterState; +use crate::util::age; pub(crate) struct DaemonsetLister {} @@ -52,7 +53,7 @@ impl Lister<(String, PodmanPodInfo)> for DaemonsetLister { fn print(&self, items: Vec<(String, PodmanPodInfo)>) { println!( "{0: <30} {1: <10} {2: <10} {3: <10} {4: <30}", - "NAME", "READY", "STATUS", "RESTARTS", "CREATED" + "NAME", "READY", "STATUS", "RESTARTS", "AGE" ); let pods = items.into_iter().fold(HashMap::>::new(), |mut acc, (depl, pod)| { acc.entry(depl).or_insert(vec![]).push(pod); @@ -71,7 +72,7 @@ impl Lister<(String, PodmanPodInfo)> for DaemonsetLister { println!( "{0: <30} {1: <10} {2: <10} {3: <10} {4: <30}", - deployment, format!("{}/{}", health_pods, all_pods), "", "", created.to_rfc3339_opts(SecondsFormat::Secs, true) + deployment, format!("{}/{}", health_pods, all_pods), "", "", age(created) ) } } diff --git a/src/get/deployment.rs b/src/get/deployment.rs index 120d45d..e3f7f66 100644 --- a/src/get/deployment.rs +++ b/src/get/deployment.rs @@ -4,6 +4,7 @@ use itertools::Itertools; use crate::get::{GetObjectArgs, IdCommand, Lister}; use crate::skatelet::{PodmanPodInfo, PodmanPodStatus, SystemInfo}; use crate::state::state::ClusterState; +use crate::util::age; pub(crate) struct DeploymentLister {} @@ -56,7 +57,7 @@ impl Lister<(String, PodmanPodInfo)> for DeploymentLister { fn print(&self, items: Vec<(String, PodmanPodInfo)>) { println!( "{0: <30} {1: <10} {2: <10} {3: <10} {4: <30}", - "NAME", "READY", "STATUS", "RESTARTS", "CREATED" + "NAME", "READY", "STATUS", "RESTARTS", "AGE" ); let pods = items.into_iter().fold(HashMap::>::new(), |mut acc, (depl, pod)| { acc.entry(depl).or_insert(vec![]).push(pod); @@ -75,7 +76,7 @@ impl Lister<(String, PodmanPodInfo)> for DeploymentLister { println!( "{0: <30} {1: <10} {2: <10} {3: <10} {4: <30}", - deployment, format!("{}/{}", health_pods, all_pods), "", "", created.to_rfc3339_opts(SecondsFormat::Secs, true) + deployment, format!("{}/{}", health_pods, all_pods), "", "", age(created) ) } } diff --git a/src/get/pod.rs b/src/get/pod.rs index 11cedf9..2cc9b43 100644 --- a/src/get/pod.rs +++ b/src/get/pod.rs @@ -2,6 +2,7 @@ use chrono::SecondsFormat; use crate::get::{Lister}; use crate::get::lister::NameFilters; use crate::skatelet::{PodmanPodInfo, SystemInfo}; +use crate::util::age; pub (crate) struct PodLister {} @@ -32,7 +33,7 @@ impl Lister for PodLister { fn print(&self, pods: Vec) { println!( "{0: <30} {1: <10} {2: <10} {3: <10} {4: <30}", - "NAME", "READY", "STATUS", "RESTARTS", "CREATED" + "NAME", "READY", "STATUS", "RESTARTS", "AGE" ); for pod in pods { let num_containers = pod.containers.clone().unwrap_or_default().len(); @@ -46,7 +47,7 @@ impl Lister for PodLister { .reduce(|a, c| a + c).unwrap_or_default(); println!( "{0: <30} {1: <10} {2: <10} {3: <10} {4: <30}", - pod.name, format!("{}/{}", healthy_containers, num_containers), pod.status, restarts, pod.created.to_rfc3339_opts(SecondsFormat::Secs, true) + pod.name, format!("{}/{}", healthy_containers, num_containers), pod.status, restarts, age(pod.created) ) } } diff --git a/src/get/secret.rs b/src/get/secret.rs new file mode 100644 index 0000000..e52c9bc --- /dev/null +++ b/src/get/secret.rs @@ -0,0 +1,39 @@ +use std::collections::HashMap; +use chrono::{Local, SecondsFormat}; +use itertools::Itertools; +use crate::filestore::ObjectListItem; +use crate::get::{GetObjectArgs, IdCommand, Lister}; +use crate::skatelet::{PodmanPodInfo, PodmanPodStatus, SystemInfo}; +use crate::state::state::ClusterState; +use crate::util::age; + +pub(crate) struct SecretLister {} + +impl Lister for SecretLister { + fn selector(&self, si: &SystemInfo, ns: &str, id: &str) -> Option> { + si.secrets.clone() + } + + fn print(&self, items: Vec) { + let map = items.iter().fold(HashMap::>::new(), |mut acc, item| { + acc.entry(item.name.to_string()).or_insert(vec![]).push(item.clone()); + acc + }); + + macro_rules! cols { + () => ("{0: <15} {1: <15} {2: <15} {3: <15} {4: <10}") + } + println!( + cols!(), + "NAMESPACE", "NAME", "TYPE", "DATA", "AGE", + ); + + // TODO - get from manifest + let data = 1; + + for item in map { + let item = item.1.first().unwrap(); + println!(cols!(), item.name.namespace, item.name.name, "Opaque", data, age(item.created_at)) + } + } +} diff --git a/src/skatelet/system.rs b/src/skatelet/system.rs index 965d385..0706803 100644 --- a/src/skatelet/system.rs +++ b/src/skatelet/system.rs @@ -1,3 +1,5 @@ +mod podman; + use std::collections::{BTreeMap}; use std::env::consts::ARCH; use sysinfo::{CpuRefreshKind, DiskKind, Disks, MemoryRefreshKind, RefreshKind, System}; @@ -8,14 +10,16 @@ use anyhow::anyhow; use chrono::{DateTime, Local}; use clap::{Args, Subcommand}; -use k8s_openapi::api::core::v1::{Pod, PodSpec, PodStatus as K8sPodStatus}; +use k8s_openapi::api::core::v1::{Pod, PodSpec, PodStatus as K8sPodStatus, Secret}; use k8s_openapi::apimachinery::pkg::apis::meta::v1::ObjectMeta; use serde::{Deserialize, Serialize}; +use serde_yaml::Value; use strum_macros::{Display, EnumString}; use crate::filestore::{FileStore, ObjectListItem}; use crate::skate::{Distribution, exec_cmd, Platform}; - +use crate::skatelet::system::podman::PodmanSecret; +use crate::util::NamespacedName; #[derive(Debug, Args)] @@ -57,6 +61,7 @@ pub struct SystemInfo { pub pods: Option>, pub ingresses: Option>, pub cronjobs: Option>, + pub secrets: Option>, pub cpu_freq_mhz: u64, pub cpu_usage: f32, pub cpu_brand: String, @@ -295,7 +300,7 @@ async fn info() -> Result<(), Box> { .with_memory(MemoryRefreshKind::everything()) ); - let result = match exec_cmd( + let pod_list_result = match exec_cmd( "sudo", &["podman", "pod", "ps", "--filter", "label=skate.io/namespace", "--format", "json"], ) { @@ -310,7 +315,7 @@ async fn info() -> Result<(), Box> { } }; - let podman_pod_info: Vec = serde_json::from_str(&result).map_err(|e| anyhow!(e).context("failed to deserialize pod info"))?; + let podman_pod_info: Vec = serde_json::from_str(&pod_list_result).map_err(|e| anyhow!(e).context("failed to deserialize pod info"))?; let store = FileStore::new(); @@ -319,6 +324,48 @@ async fn info() -> Result<(), Box> { let cronjobs = store.list_objects("cronjob")?; + let secrets = exec_cmd("podman", &["secret", "ls", "--noheading"]).unwrap_or_else(|e| { + eprintln!("failed to list secrets: {}", e); + "".to_string() + }); + + let secret_names: Vec<&str> = secrets.split("\n").filter_map(|line| { + let parts: Vec<&str> = line.split_whitespace().collect(); + if parts.len() < 5 { + return None; + } + let secret_name = parts[1]; + match secret_name.rsplit_once(".") { + Some((_, _)) => Some(secret_name), + None => None, + } + }).collect(); + + let secret_json = exec_cmd("podman", &[vec!["secret", "inspect", "--showsecret"], secret_names].concat()).unwrap_or_else(|e| { + eprintln!("failed to get secret info: {}", e); + "[]".to_string() + }); + + + let secret_info: Vec = serde_json::from_str(&secret_json).map_err(|e| anyhow!(e).context("failed to deserialize secret info"))?; + let secret_info: Vec = secret_info.iter().filter_map(|s| { + + let yaml: Value = serde_yaml::from_str(&s.secret_data).unwrap(); + + let manifest_result: Result = serde_yaml::from_value(yaml.clone()); + if manifest_result.is_err() { + return None; + } + + Some(ObjectListItem { + name: NamespacedName::from(s.spec.name.as_str()), + manifest_hash: "".to_string(), // TODO get from manifest + manifest: Some(yaml), + created_at: s.created_at, + }) + }).collect(); + + let internal_ip_addr = internal_ip().unwrap_or_else(|e| { eprintln!("failed to get interface ipv4 addresses: {}", e); None @@ -361,6 +408,10 @@ async fn info() -> Result<(), Box> { true => None, false => Some(cronjobs), }, + secrets: match secrets.is_empty() { + true => None, + false => Some(secret_info), + }, hostname: sysinfo::System::host_name().unwrap_or("".to_string()), internal_ip_address: internal_ip_addr, }; diff --git a/src/skatelet/system/podman.rs b/src/skatelet/system/podman.rs new file mode 100644 index 0000000..6312c89 --- /dev/null +++ b/src/skatelet/system/podman.rs @@ -0,0 +1,29 @@ +use std::collections::HashMap; +use chrono::{DateTime, Local}; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "PascalCase")] +pub(crate) struct PodmanSecret { + #[serde(rename = "ID")] + pub id: String, + pub created_at: DateTime, + pub updated_at: DateTime, + pub spec: PodmanSecretSpec, + pub secret_data: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "PascalCase")] +pub(crate) struct PodmanSecretSpec { + pub name: String, + pub driver: PodmanSecretDriver, + pub labels: HashMap, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "PascalCase")] +pub(crate) struct PodmanSecretDriver { + pub name: String, + pub options: HashMap, +} \ No newline at end of file