From a1af7bd3df0700a7c1e70377a8fabd60e50c7f0a Mon Sep 17 00:00:00 2001 From: Donal Byrne Date: Sat, 3 Aug 2024 07:59:29 +0200 Subject: [PATCH] Fixed listing for daemonsets and secrets --- src/create.rs | 11 +- src/delete.rs | 1 - src/describe.rs | 1 - src/get/cronjob.rs | 2 +- src/get/daemonset.rs | 35 ++++--- src/get/deployment.rs | 5 +- src/get/pod.rs | 5 +- src/scheduler.rs | 2 +- src/skatelet/mod.rs | 4 +- src/skatelet/system.rs | 189 +--------------------------------- src/skatelet/system/podman.rs | 186 ++++++++++++++++++++++++++++++++- src/skatelet/template.rs | 4 - src/state/state.rs | 2 +- 13 files changed, 225 insertions(+), 222 deletions(-) diff --git a/src/create.rs b/src/create.rs index e2caaed..882b5c2 100644 --- a/src/create.rs +++ b/src/create.rs @@ -172,7 +172,7 @@ async fn create_node(args: CreateNodeArgs) -> Result<(), Box> { config.persist(Some(args.config.skateconfig.clone()))?; - /// Refresh state so that we can apply coredns later + // Refresh state so that we can apply coredns later let state = refreshed_state(&cluster.name, &all_conns, &config).await?; state.persist()?; @@ -182,10 +182,11 @@ async fn create_node(args: CreateNodeArgs) -> Result<(), Box> { } async fn install_manifests(args: &CreateNodeArgs, config: &Cluster, node: &Node) -> Result<(), Box> { - /// COREDNS - /// coredns listens on port 53 and 5533 - /// port 53 serves .cluster.skate by forwarding to all coredns instances on port 5553 - /// uses fanout plugin + + // COREDNS + // coredns listens on port 53 and 5533 + // port 53 serves .cluster.skate by forwarding to all coredns instances on port 5553 + // uses fanout plugin // replace forward list in coredns config with that of other hosts let fanout_list = config.nodes.iter().map(|n| n.host.clone() + ":5553").join(" "); diff --git a/src/delete.rs b/src/delete.rs index 747bb0c..00c5200 100644 --- a/src/delete.rs +++ b/src/delete.rs @@ -41,7 +41,6 @@ pub async fn delete(args: DeleteArgs) -> Result<(), Box> { DeleteCommands::Ingress(args) => delete_resource(ResourceType::Ingress, args).await?, DeleteCommands::Cronjob(args) => delete_resource(ResourceType::CronJob, args).await?, DeleteCommands::Secret(args) => delete_resource(ResourceType::Secret, args).await?, - _ => todo!() } Ok(()) } diff --git a/src/describe.rs b/src/describe.rs index 52c1528..c04915b 100644 --- a/src/describe.rs +++ b/src/describe.rs @@ -59,7 +59,6 @@ impl Describer for NodeDescriber { fn find(&self, filters: &DescribeObjectArgs, state: &ClusterState) -> Option { let id = filters.id.as_ref().and_then(|cmd| match cmd { IdCommand::Id(ids) => ids.first().and_then(|id| Some((*id).clone())), - _ => None }); let id = match id { Some(id) => id, diff --git a/src/get/cronjob.rs b/src/get/cronjob.rs index c227bc6..9eedecf 100644 --- a/src/get/cronjob.rs +++ b/src/get/cronjob.rs @@ -19,7 +19,7 @@ impl Lister for CronjobsLister { // TODO - record last run and how many running (somehow) fn print(&self, resources: Vec) { - macro_rules! cols { () => ("{0: <10} {1: <10} {2: <10} {3: <10} {4: <10} {5: <10} {6: <15} {7: <10}") }; + macro_rules! cols { () => ("{0: <10} {1: <10} {2: <10} {3: <10} {4: <10} {5: <10} {6: <15} {7: <10}") } println!( cols!(), "NAMESPACE", "NAME", "SCHEDULE", "TIMEZONE", "SUSPEND", "ACTIVE", "LAST SCHEDULE", "AGE" diff --git a/src/get/daemonset.rs b/src/get/daemonset.rs index 0438d2a..fbd6ff0 100644 --- a/src/get/daemonset.rs +++ b/src/get/daemonset.rs @@ -1,18 +1,19 @@ use std::collections::HashMap; -use chrono::{Local}; +use chrono::Local; use itertools::Itertools; use crate::get::{GetObjectArgs, IdCommand, Lister}; -use crate::skatelet::{PodmanPodInfo, PodmanPodStatus, SystemInfo}; +use crate::skatelet::SystemInfo; +use crate::skatelet::system::podman::{PodmanPodInfo, PodmanPodStatus}; use crate::state::state::ClusterState; use crate::util::age; pub(crate) struct DaemonsetLister {} -impl Lister<(String, PodmanPodInfo)> for DaemonsetLister { - fn selector(&self, _si: &SystemInfo, _ns: &str, _id: &str) -> Option> { +impl Lister<(usize, String, PodmanPodInfo)> for DaemonsetLister { + fn selector(&self, _si: &SystemInfo, _ns: &str, _id: &str) -> Option> { todo!() } - fn list(&self, args: &GetObjectArgs, state: &ClusterState) -> Vec<(String, PodmanPodInfo)> { + fn list(&self, args: &GetObjectArgs, state: &ClusterState) -> Vec<(usize, String, PodmanPodInfo)> { let pods: Vec<_> = state.nodes.iter().filter_map(|n| { let items: Vec<_> = n.host_info.clone()?.system_info?.pods.unwrap_or_default().into_iter().filter_map(|p| { let ns = args.namespace.clone().unwrap_or("default".to_string()); @@ -38,7 +39,7 @@ impl Lister<(String, PodmanPodInfo)> for DaemonsetLister { None => false }; if match_ns || match_id || (id.is_none() && ns == "" && daemonset_ns != "skate" ) { - return Some((daemonset, p)); + return Some((state.nodes.len(), daemonset, p)); } None }).collect(); @@ -50,17 +51,22 @@ impl Lister<(String, PodmanPodInfo)> for DaemonsetLister { pods } - fn print(&self, items: Vec<(String, PodmanPodInfo)>) { + fn print(&self, items: Vec<(usize, String, PodmanPodInfo)>) { + // NAMESPACE NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE + macro_rules! cols { + () => ("{0: <15} {1: <15} {2: <12} {3: <12} {4: <12} {5: <12} {6: <12} {7: <50} {8: <15}") + } println!( - "{0: <30} {1: <10} {2: <10} {3: <10} {4: <30}", - "NAME", "READY", "STATUS", "RESTARTS", "AGE" + cols!(), + "NAMESPACE", "NAME", "DESIRED", "CURRENT", "READY", "UP-TO-DATE", "AVAILABLE", "NODE SELECTOR", "AGE" ); - let pods = items.into_iter().fold(HashMap::>::new(), |mut acc, (depl, pod)| { + let num_nodes = items.first().unwrap().0; + let pods = items.into_iter().fold(HashMap::>::new(), |mut acc, (num_nodes, depl, pod)| { acc.entry(depl).or_insert(vec![]).push(pod); acc }); - for (deployment, pods) in pods { + for (name, pods) in pods { let health_pods = pods.iter().filter(|p| PodmanPodStatus::Running == p.status).collect_vec().len(); let all_pods = pods.len(); let created = pods.iter().fold(Local::now(), |acc, item| { @@ -69,10 +75,13 @@ impl Lister<(String, PodmanPodInfo)> for DaemonsetLister { } return acc; }); + let namespace = pods.first().unwrap().labels.get("skate.io/namespace").unwrap_or(&"default".to_string()).clone(); + let node_selector = pods.first().unwrap().labels.iter().filter(|(k, _)| k.starts_with("nodeselector/")).map(|(k, v)| format!("{}={}", k, v)).collect_vec().join(","); + // assuming that we want same number as nodes, that's wrong but anyway println!( - "{0: <30} {1: <10} {2: <10} {3: <10} {4: <30}", - deployment, format!("{}/{}", health_pods, all_pods), "", "", age(created) + cols!(), + namespace, name, num_nodes, pods.len(), health_pods, "", "", node_selector, age(created) ) } } diff --git a/src/get/deployment.rs b/src/get/deployment.rs index c50cc55..aed5b70 100644 --- a/src/get/deployment.rs +++ b/src/get/deployment.rs @@ -1,8 +1,9 @@ use std::collections::HashMap; -use chrono::{Local}; +use chrono::Local; use itertools::Itertools; use crate::get::{GetObjectArgs, IdCommand, Lister}; -use crate::skatelet::{PodmanPodInfo, PodmanPodStatus, SystemInfo}; +use crate::skatelet::SystemInfo; +use crate::skatelet::system::podman::{PodmanPodInfo, PodmanPodStatus}; use crate::state::state::ClusterState; use crate::util::age; diff --git a/src/get/pod.rs b/src/get/pod.rs index 4f47af9..df5def1 100644 --- a/src/get/pod.rs +++ b/src/get/pod.rs @@ -1,7 +1,8 @@ -use crate::get::{Lister}; +use crate::get::Lister; use crate::get::lister::NameFilters; -use crate::skatelet::{PodmanPodInfo, SystemInfo}; +use crate::skatelet::SystemInfo; +use crate::skatelet::system::podman::PodmanPodInfo; use crate::util::age; pub (crate) struct PodLister {} diff --git a/src/scheduler.rs b/src/scheduler.rs index a1c7e19..11e8050 100644 --- a/src/scheduler.rs +++ b/src/scheduler.rs @@ -13,7 +13,7 @@ use k8s_openapi::Metadata; use crate::skate::SupportedResources; -use crate::skatelet::PodmanPodStatus; +use crate::skatelet::system::podman::PodmanPodStatus; use crate::ssh::{SshClients}; use crate::state::state::{ClusterState, NodeState}; use crate::util::{CHECKBOX_EMOJI, CROSS_EMOJI, EQUAL_EMOJI, hash_k8s_resource, INFO_EMOJI, metadata_name}; diff --git a/src/skatelet/mod.rs b/src/skatelet/mod.rs index 3d98785..1c696df 100644 --- a/src/skatelet/mod.rs +++ b/src/skatelet/mod.rs @@ -1,13 +1,11 @@ mod skatelet; mod apply; -mod system; +pub(crate) mod system; mod cni; mod template; mod delete; pub use skatelet::skatelet; pub use system::SystemInfo; -pub use system::PodmanPodInfo; -pub use system::PodmanPodStatus; diff --git a/src/skatelet/system.rs b/src/skatelet/system.rs index 0706803..b38fc83 100644 --- a/src/skatelet/system.rs +++ b/src/skatelet/system.rs @@ -1,20 +1,17 @@ -mod podman; +pub(crate) mod podman; -use std::collections::{BTreeMap}; use std::env::consts::ARCH; use sysinfo::{CpuRefreshKind, DiskKind, Disks, MemoryRefreshKind, RefreshKind, System}; use std::error::Error; use anyhow::anyhow; -use chrono::{DateTime, Local}; use clap::{Args, Subcommand}; -use k8s_openapi::api::core::v1::{Pod, PodSpec, PodStatus as K8sPodStatus, Secret}; -use k8s_openapi::apimachinery::pkg::apis::meta::v1::ObjectMeta; +use k8s_openapi::api::core::v1::Secret; use serde::{Deserialize, Serialize}; use serde_yaml::Value; -use strum_macros::{Display, EnumString}; +use podman::PodmanPodInfo; use crate::filestore::{FileStore, ObjectListItem}; use crate::skate::{Distribution, exec_cmd, Platform}; @@ -70,188 +67,8 @@ pub struct SystemInfo { pub hostname: String, } - -#[derive(Clone, Debug, EnumString, Display, Serialize, Deserialize, PartialEq)] -pub enum PodmanPodStatus { - Created, - Running, - Stopped, - Exited, - Dead, - Degraded, - Error, -} - -impl PodmanPodStatus { - fn to_pod_phase(self) -> String { - match self { - PodmanPodStatus::Running => "Running", - PodmanPodStatus::Stopped => "Succeeded", - PodmanPodStatus::Exited => "Succeeded", - PodmanPodStatus::Dead => "Failed", - PodmanPodStatus::Degraded => "Running", - PodmanPodStatus::Created => "Pending", - PodmanPodStatus::Error => "Failed", - }.to_string() - } - fn from_pod_phase(phase: &str) -> Self { - match phase { - "Running" => PodmanPodStatus::Running, - // lossy - "Succeeded" => PodmanPodStatus::Exited, - "Failed" => PodmanPodStatus::Dead, - "Pending" => PodmanPodStatus::Created, - _ => PodmanPodStatus::Created, - } - } -} - // TODO - have more generic ObjectMeta type for explaining existing resources -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "PascalCase")] -pub struct PodmanPodInfo { - pub id: String, - pub name: String, - pub status: PodmanPodStatus, - pub created: DateTime, - pub labels: BTreeMap, - pub containers: Option>, -} - - -impl PodmanPodInfo { - pub fn namespace(&self) -> String { - self.labels.get("skate.io/namespace").map(|ns| ns.clone()).unwrap_or("".to_string()) - } - pub fn deployment(&self) -> String { - self.labels.get("skate.io/deployment").map(|d| d.clone()).unwrap_or("".to_string()) - } -} - -impl From for PodmanPodInfo { - fn from(value: Pod) -> Self { - PodmanPodInfo { - id: value.metadata.uid.unwrap_or("".to_string()), - name: value.metadata.name.unwrap_or("".to_string()), - status: PodmanPodStatus::from_pod_phase(value.status.and_then(|s| s.phase.and_then(|p| { - Some(p) - })).unwrap_or("".to_string()).as_str()), - created: value.metadata.creation_timestamp.and_then(|ts| Some(DateTime::from(ts.0))).unwrap_or(DateTime::from(Local::now())), - labels: value.metadata.labels.unwrap_or(BTreeMap::new()), - containers: None, // TODO - } - } -} - -impl Into for PodmanPodInfo { - fn into(self) -> Pod { - Pod { - metadata: ObjectMeta { - annotations: None, - creation_timestamp: Some(k8s_openapi::apimachinery::pkg::apis::meta::v1::Time(DateTime::from(self.created))), - deletion_grace_period_seconds: None, - deletion_timestamp: None, - finalizers: None, - generate_name: None, - generation: None, - labels: match self.labels.len() { - 0 => None, - _ => Some(self.labels.iter().filter_map(|(k, v)| { - if k.starts_with("nodeselector/") { - None - } else { - Some((k.clone(), v.clone())) - } - }).collect()) - }, - managed_fields: None, - name: Some(self.name.clone()), - namespace: Some(self.namespace()), - owner_references: None, - resource_version: None, - self_link: None, - uid: Some(self.id), - }, - spec: Some(PodSpec { - active_deadline_seconds: None, - affinity: None, - automount_service_account_token: None, - containers: vec![], - dns_config: None, - dns_policy: None, - enable_service_links: None, - ephemeral_containers: None, - host_aliases: None, - host_ipc: None, - host_network: None, - host_pid: None, - host_users: None, - hostname: None, - image_pull_secrets: None, - init_containers: None, - node_name: None, - node_selector: Some(self.labels.iter().filter_map(|(k, v)| { - if k.starts_with("nodeselector/") { - Some(((*k.clone()).strip_prefix("nodeselector/").unwrap_or(k).to_string(), (*v).clone())) - } else { - None - } - }).collect::>()), - os: None, - overhead: None, - preemption_policy: None, - priority: None, - priority_class_name: None, - readiness_gates: None, - resource_claims: None, - restart_policy: None, - runtime_class_name: None, - scheduler_name: None, - scheduling_gates: None, - security_context: None, - service_account: None, - service_account_name: None, - set_hostname_as_fqdn: None, - share_process_namespace: None, - subdomain: None, - termination_grace_period_seconds: None, - tolerations: None, - topology_spread_constraints: None, - volumes: None, - }), // TODO - status: Some(K8sPodStatus - { - conditions: None, - container_statuses: None, - ephemeral_container_statuses: None, - host_ip: None, - host_ips: None, - init_container_statuses: None, - message: None, - nominated_node_name: None, - phase: Some(self.status.to_pod_phase()), - pod_ip: None, - pod_ips: None, - qos_class: None, - reason: None, - resize: None, - resource_claim_statuses: None, - start_time: None, - }), - } - } -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "PascalCase")] -pub struct PodmanContainerInfo { - pub id: String, - pub names: String, - pub status: String, - pub restart_count: Option, -} - // returns (external, internal) fn internal_ip() -> Result, Box> { let iface_cmd = match exec_cmd("which", &["ifconfig"]) { diff --git a/src/skatelet/system/podman.rs b/src/skatelet/system/podman.rs index 6312c89..8af0a8c 100644 --- a/src/skatelet/system/podman.rs +++ b/src/skatelet/system/podman.rs @@ -1,6 +1,9 @@ -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap}; use chrono::{DateTime, Local}; use serde::{Deserialize, Serialize}; +use k8s_openapi::api::core::v1::{Pod, PodSpec, PodStatus as K8sPodStatus, Secret}; +use k8s_openapi::apimachinery::pkg::apis::meta::v1::ObjectMeta; +use strum_macros::{Display, EnumString}; #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "PascalCase")] @@ -26,4 +29,183 @@ pub(crate) struct PodmanSecretSpec { pub(crate) struct PodmanSecretDriver { pub name: String, pub options: HashMap, -} \ No newline at end of file +} + +#[derive(Clone, Debug, EnumString, Display, Serialize, Deserialize, PartialEq)] +pub(crate) enum PodmanPodStatus { + Created, + Running, + Stopped, + Exited, + Dead, + Degraded, + Error, +} + +impl PodmanPodStatus { + fn to_pod_phase(self) -> String { + match self { + PodmanPodStatus::Running => "Running", + PodmanPodStatus::Stopped => "Succeeded", + PodmanPodStatus::Exited => "Succeeded", + PodmanPodStatus::Dead => "Failed", + PodmanPodStatus::Degraded => "Running", + PodmanPodStatus::Created => "Pending", + PodmanPodStatus::Error => "Failed", + }.to_string() + } + fn from_pod_phase(phase: &str) -> Self { + match phase { + "Running" => PodmanPodStatus::Running, + // lossy + "Succeeded" => PodmanPodStatus::Exited, + "Failed" => PodmanPodStatus::Dead, + "Pending" => PodmanPodStatus::Created, + _ => PodmanPodStatus::Created, + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "PascalCase")] +pub(crate) struct PodmanPodInfo { + pub id: String, + pub name: String, + pub status: PodmanPodStatus, + pub created: DateTime, + pub labels: BTreeMap, + pub containers: Option>, +} + + +impl PodmanPodInfo { + pub fn namespace(&self) -> String { + self.labels.get("skate.io/namespace").map(|ns| ns.clone()).unwrap_or("".to_string()) + } + pub fn deployment(&self) -> String { + self.labels.get("skate.io/deployment").map(|d| d.clone()).unwrap_or("".to_string()) + } +} + +impl From for PodmanPodInfo { + fn from(value: Pod) -> Self { + PodmanPodInfo { + id: value.metadata.uid.unwrap_or("".to_string()), + name: value.metadata.name.unwrap_or("".to_string()), + status: PodmanPodStatus::from_pod_phase(value.status.and_then(|s| s.phase.and_then(|p| { + Some(p) + })).unwrap_or("".to_string()).as_str()), + created: value.metadata.creation_timestamp.and_then(|ts| Some(DateTime::from(ts.0))).unwrap_or(DateTime::from(Local::now())), + labels: value.metadata.labels.unwrap_or(BTreeMap::new()), + containers: None, // TODO + } + } +} + +impl Into for PodmanPodInfo { + fn into(self) -> Pod { + Pod { + metadata: ObjectMeta { + annotations: None, + creation_timestamp: Some(k8s_openapi::apimachinery::pkg::apis::meta::v1::Time(DateTime::from(self.created))), + deletion_grace_period_seconds: None, + deletion_timestamp: None, + finalizers: None, + generate_name: None, + generation: None, + labels: match self.labels.len() { + 0 => None, + _ => Some(self.labels.iter().filter_map(|(k, v)| { + if k.starts_with("nodeselector/") { + None + } else { + Some((k.clone(), v.clone())) + } + }).collect()) + }, + managed_fields: None, + name: Some(self.name.clone()), + namespace: Some(self.namespace()), + owner_references: None, + resource_version: None, + self_link: None, + uid: Some(self.id), + }, + spec: Some(PodSpec { + active_deadline_seconds: None, + affinity: None, + automount_service_account_token: None, + containers: vec![], + dns_config: None, + dns_policy: None, + enable_service_links: None, + ephemeral_containers: None, + host_aliases: None, + host_ipc: None, + host_network: None, + host_pid: None, + host_users: None, + hostname: None, + image_pull_secrets: None, + init_containers: None, + node_name: None, + node_selector: Some(self.labels.iter().filter_map(|(k, v)| { + if k.starts_with("nodeselector/") { + Some(((*k.clone()).strip_prefix("nodeselector/").unwrap_or(k).to_string(), (*v).clone())) + } else { + None + } + }).collect::>()), + os: None, + overhead: None, + preemption_policy: None, + priority: None, + priority_class_name: None, + readiness_gates: None, + resource_claims: None, + restart_policy: None, + runtime_class_name: None, + scheduler_name: None, + scheduling_gates: None, + security_context: None, + service_account: None, + service_account_name: None, + set_hostname_as_fqdn: None, + share_process_namespace: None, + subdomain: None, + termination_grace_period_seconds: None, + tolerations: None, + topology_spread_constraints: None, + volumes: None, + }), // TODO + status: Some(K8sPodStatus + { + conditions: None, + container_statuses: None, + ephemeral_container_statuses: None, + host_ip: None, + host_ips: None, + init_container_statuses: None, + message: None, + nominated_node_name: None, + phase: Some(self.status.to_pod_phase()), + pod_ip: None, + pod_ips: None, + qos_class: None, + reason: None, + resize: None, + resource_claim_statuses: None, + start_time: None, + }), + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "PascalCase")] +pub(crate) struct PodmanContainerInfo { + pub id: String, + pub names: String, + pub status: String, + pub restart_count: Option, +} diff --git a/src/skatelet/template.rs b/src/skatelet/template.rs index a47ab87..2e19594 100644 --- a/src/skatelet/template.rs +++ b/src/skatelet/template.rs @@ -23,10 +23,6 @@ pub struct TemplateArgs { } pub fn template(template_args: TemplateArgs) -> Result<(), Box> { - let _manifest = match template_args.command { - StdinJsonCommand::Stdin {} => Ok(()), - _ => Err(anyhow!("must give '-' to use stdin as input")) - }?; let mut handlebars = Handlebars::new(); handlebars.set_strict_mode(true); diff --git a/src/state/state.rs b/src/state/state.rs index 937dda9..9866401 100644 --- a/src/state/state.rs +++ b/src/state/state.rs @@ -14,7 +14,7 @@ use crate::config::{cache_dir, Config}; use crate::filestore::ObjectListItem; use crate::skate::SupportedResources; -use crate::skatelet::PodmanPodInfo; +use crate::skatelet::system::podman::PodmanPodInfo; use crate::ssh::HostInfo; use crate::state::state::NodeStatus::{Healthy, Unhealthy, Unknown}; use crate::util::{hash_string, slugify};