diff --git a/hack/test-secret.yaml b/hack/test-secret.yaml new file mode 100644 index 0000000..aef360c --- /dev/null +++ b/hack/test-secret.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: Secret +metadata: + name: test + namespace: foo +type: Opaque +data: + username: dXNlcg== + password: NTRmNDFkMTJlOGZh \ No newline at end of file diff --git a/src/delete.rs b/src/delete.rs index 14640dd..d9b6883 100644 --- a/src/delete.rs +++ b/src/delete.rs @@ -20,6 +20,7 @@ pub enum DeleteCommands { Node(DeleteResourceArgs), Ingress(DeleteResourceArgs), Cronjob(DeleteResourceArgs), + Secret(DeleteResourceArgs), } #[derive(Debug, Args)] @@ -39,6 +40,8 @@ pub async fn delete(args: DeleteArgs) -> Result<(), Box> { DeleteCommands::Node(args) => delete_node(args).await?, 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(()) } @@ -65,7 +68,7 @@ async fn delete_resource(r_type: ResourceType, args: DeleteResourceArgs) -> Resu match conn.remove_resource(r_type.clone(), &args.name, &args.namespace).await { Ok(result) => { results.push(result) - }, + } Err(e) => errors.push(e.to_string()) } } diff --git a/src/executor.rs b/src/executor.rs index 0467f7a..e6b4c39 100644 --- a/src/executor.rs +++ b/src/executor.rs @@ -8,7 +8,7 @@ use anyhow::anyhow; use handlebars::Handlebars; use k8s_openapi::api::batch::v1::CronJob; -use k8s_openapi::api::core::v1::Pod; +use k8s_openapi::api::core::v1::{Pod, Secret}; use k8s_openapi::api::networking::v1::Ingress; use serde_json::{json, Value}; @@ -271,6 +271,20 @@ impl DefaultExecutor { Ok(()) } + + fn remove_secret(&self, secret: Secret) -> Result<(), Box> { + let fqn = format!("{}.{}", secret.metadata.name.unwrap(), secret.metadata.namespace.unwrap()); + + let _output = process::Command::new("podman") + .args(["secret", "rm", &fqn]) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .output() + .expect("failed to remove secret"); + + Ok(()) + } + fn remove_pod(&self, id: &str, _namespace: &str, grace_period: Option) -> Result<(), Box> { if id.is_empty() { return Err(anyhow!("no metadata.name found").into()); @@ -349,7 +363,7 @@ impl Executor for DefaultExecutor { self.remove_cron(cron) } SupportedResources::Secret(secret) => { - todo!() + self.remove_secret(secret) } } } diff --git a/src/get.rs b/src/get.rs index d3aec3a..39215de 100644 --- a/src/get.rs +++ b/src/get.rs @@ -4,6 +4,7 @@ mod deployment; mod cronjob; mod pod; mod lister; +mod daemonset; use std::error::Error; @@ -21,6 +22,7 @@ use crate::skate::{ConfigFileArgs}; use crate::{ssh}; use crate::get::cronjob::CronjobsLister; +use crate::get::daemonset::DaemonsetLister; use crate::get::deployment::DeploymentLister; use crate::get::ingress::IngresssLister; use crate::get::lister::Lister; @@ -118,6 +120,11 @@ async fn get_deployment(global_args: GetArgs, args: GetObjectArgs) -> Result<(), get_objects(global_args, args, &lister).await } +async fn get_daemonsets(global_args: GetArgs, args: GetObjectArgs) -> Result<(), Box> { + let lister = DaemonsetLister {}; + get_objects(global_args, args, &lister).await +} + async fn get_pod(global_args: GetArgs, args: GetObjectArgs) -> Result<(), Box> { let lister = PodLister {}; get_objects(global_args, args, &lister).await diff --git a/src/get/daemonset.rs b/src/get/daemonset.rs new file mode 100644 index 0000000..bd5deb8 --- /dev/null +++ b/src/get/daemonset.rs @@ -0,0 +1,78 @@ +use std::collections::HashMap; +use chrono::{Local, SecondsFormat}; +use itertools::Itertools; +use crate::get::{GetObjectArgs, IdCommand, Lister}; +use crate::skatelet::{PodmanPodInfo, PodmanPodStatus, SystemInfo}; +use crate::state::state::ClusterState; + +pub(crate) struct DaemonsetLister {} + +impl Lister<(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)> { + 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()); + let id = match args.id.clone() { + Some(cmd) => match cmd { + IdCommand::Id(ids) => Some(ids.into_iter().next().unwrap_or("".to_string())) + } + None => None + }; + let daemonset = p.labels.get("skate.io/daemonset").and_then(|n| Some(n.clone())).unwrap_or_default(); + if daemonset == "" { + return None; + } + let daemonset_ns = p.labels.get("skate.io/namespace").unwrap_or(&"".to_string()).clone(); + + + let match_ns = ns == daemonset_ns; + + let match_id = match id.clone() { + Some(id) => { + id == daemonset + } + None => false + }; + if match_ns || match_id || (id.is_none() && ns == "" && daemonset_ns != "skate" ) { + return Some((daemonset, p)); + } + None + }).collect(); + match items.len() { + 0 => None, + _ => Some(items) + } + }).flatten().collect(); + pods + } + + fn print(&self, items: Vec<(String, PodmanPodInfo)>) { + println!( + "{0: <30} {1: <10} {2: <10} {3: <10} {4: <30}", + "NAME", "READY", "STATUS", "RESTARTS", "CREATED" + ); + let pods = items.into_iter().fold(HashMap::>::new(), |mut acc, (depl, pod)| { + acc.entry(depl).or_insert(vec![]).push(pod); + acc + }); + + for (deployment, 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| { + if item.created < acc { + return item.created; + } + return acc; + }); + + println!( + "{0: <30} {1: <10} {2: <10} {3: <10} {4: <30}", + deployment, format!("{}/{}", health_pods, all_pods), "", "", created.to_rfc3339_opts(SecondsFormat::Secs, true) + ) + } + } +} diff --git a/src/get/deployment.rs b/src/get/deployment.rs index eed573e..120d45d 100644 --- a/src/get/deployment.rs +++ b/src/get/deployment.rs @@ -5,7 +5,7 @@ use crate::get::{GetObjectArgs, IdCommand, Lister}; use crate::skatelet::{PodmanPodInfo, PodmanPodStatus, SystemInfo}; use crate::state::state::ClusterState; -pub (crate) struct DeploymentLister {} +pub(crate) struct DeploymentLister {} impl Lister<(String, PodmanPodInfo)> for DeploymentLister { fn selector(&self, _si: &SystemInfo, _ns: &str, _id: &str) -> Option> { @@ -22,11 +22,12 @@ impl Lister<(String, PodmanPodInfo)> for DeploymentLister { None => None }; let deployment = p.labels.get("skate.io/deployment"); + let pod_ns = p.labels.get("skate.io/namespace").unwrap_or(&"default".to_string()).clone(); match deployment { Some(deployment) => { let match_ns = match ns.clone() { Some(ns) => { - ns == p.labels.get("skate.io/namespace").unwrap_or(&"".to_string()).clone() + ns == pod_ns } None => false }; @@ -36,7 +37,7 @@ impl Lister<(String, PodmanPodInfo)> for DeploymentLister { } None => false }; - if match_ns || match_id || (id.is_none() && ns.is_none()) { + if match_ns || match_id || (id.is_none() && ns.is_none() && pod_ns != "skate") { return Some((deployment.clone(), p)); } None diff --git a/src/get/ingress.rs b/src/get/ingress.rs index fc5ddc9..d49855d 100644 --- a/src/get/ingress.rs +++ b/src/get/ingress.rs @@ -13,7 +13,7 @@ pub(crate) struct IngresssLister {} impl Lister for IngresssLister { fn selector(&self, si: &SystemInfo, ns: &str, id: &str) -> Option> { si.ingresses.as_ref().and_then(|jobs| Some(jobs.iter().filter(|j| { - let filterable: Box = Box::new(j.clone()); + let filterable: Box = Box::new(*j); return filterable.filter_names(id, ns); }).map(|p| p.clone()).collect())) } diff --git a/src/scheduler.rs b/src/scheduler.rs index 8b7d33b..cc88b1b 100644 --- a/src/scheduler.rs +++ b/src/scheduler.rs @@ -7,7 +7,7 @@ use itertools::Itertools; use k8s_openapi::api::apps::v1::{DaemonSet, Deployment}; use k8s_openapi::api::batch::v1::CronJob; -use k8s_openapi::api::core::v1::{Node as K8sNode, Pod}; +use k8s_openapi::api::core::v1::{Node as K8sNode, Pod, Secret}; use k8s_openapi::api::networking::v1::Ingress; use k8s_openapi::Metadata; @@ -393,6 +393,29 @@ impl DefaultScheduler { + Ok(ApplyPlan { + actions, + }) + } + + // just apply on all nodes + fn plan_secret(state: &ClusterState, secret: &Secret) -> Result> { + + let mut actions = vec!(); + + for node in state.nodes.iter() { + + actions.extend([ + ScheduledOperation{ + resource: SupportedResources::Secret(secret.clone()), + error: None, + operation: OpType::Create, + node: Some(node.clone()), + } + ]); + } + + Ok(ApplyPlan { actions, }) @@ -469,7 +492,7 @@ impl DefaultScheduler { SupportedResources::DaemonSet(ds) => Self::plan_daemonset(state, ds), SupportedResources::Ingress(ingress) => Self::plan_ingress(state, ingress), SupportedResources::CronJob(cron) => Self::plan_cronjob(state, cron), - SupportedResources::Secret(secret) => todo!(), + SupportedResources::Secret(secret) => Self::plan_secret(state, secret), } } diff --git a/src/skate.rs b/src/skate.rs index 3174d01..9c04d79 100644 --- a/src/skate.rs +++ b/src/skate.rs @@ -89,6 +89,7 @@ pub enum ResourceType { DaemonSet, Ingress, CronJob, + Secret, } #[derive(Debug, Serialize, Deserialize, Display, Clone)] @@ -153,7 +154,16 @@ impl SupportedResources { let mut resource = self.clone(); let resource = match resource { SupportedResources::Secret(ref mut s) => { - // TODO + let original_name = s.metadata.name.clone().unwrap_or("".to_string()); + if original_name.is_empty() { + return Err(anyhow!("metadata.name is empty").into()); + } + if s.metadata.namespace.is_none() { + return Err(anyhow!("metadata.namespace is empty").into()); + } + + s.metadata = Self::fixup_metadata(s.metadata.clone(), None)?; + s.metadata.name = Some(format!("{}.{}", original_name, s.metadata.namespace.clone().unwrap())); resource } SupportedResources::CronJob(ref mut c) => { diff --git a/src/skatelet/apply.rs b/src/skatelet/apply.rs index 2b88e8c..d98a0c4 100644 --- a/src/skatelet/apply.rs +++ b/src/skatelet/apply.rs @@ -50,82 +50,3 @@ pub fn apply(apply_args: ApplyArgs) -> Result<(), Box> { executor.apply(&manifest) } -#[derive(Debug, Args, Clone)] -pub struct DeleteResourceArgs { - #[arg(long, long_help = "Name of the resource.")] - pub name: String, - #[arg(long, long_help = "Name of the resource.")] - pub namespace: String, -} - -#[derive(Debug, Subcommand, Clone)] -pub enum DeleteResourceCommands { - #[command(flatten)] - StdinCommand(StdinCommand), - Ingress(DeleteResourceArgs), - Cronjob(DeleteResourceArgs) -} - -#[derive(Debug, Args, Clone)] -pub struct DeleteArgs { - #[arg(short, long, long_help("Number of seconds to wait before hard killing."))] - termination_grace_period: Option, - #[command(subcommand)] - command: DeleteResourceCommands, -} - -pub fn delete(args: DeleteArgs) -> Result<(), Box> { - match &args.command { - DeleteResourceCommands::Ingress(resource_args) => delete_ingress(args.clone(), resource_args.clone()), - DeleteResourceCommands::StdinCommand(_) => delete_stdin(args), - DeleteResourceCommands::Cronjob(resource_args) => delete_cronjob(args.clone(), resource_args.clone()), - } -} - - -pub fn delete_ingress(delete_args: DeleteArgs, resource_args: DeleteResourceArgs) -> Result<(), Box> { - let executor = DefaultExecutor::new(); - let mut meta = ObjectMeta::default(); - meta.name = Some(resource_args.name.clone()); - meta.namespace = Some(resource_args.namespace.clone()); - meta.labels = Some(BTreeMap::from([ - ("skate.io/name".to_string(), resource_args.name), - ("skate.io/namespace".to_string(), resource_args.namespace), - ])); - - executor.manifest_delete(Ingress(K8sIngress { - metadata: meta, - spec: None, - status: None, - }), delete_args.termination_grace_period) -} - -pub fn delete_cronjob(delete_args: DeleteArgs, resource_args: DeleteResourceArgs) -> Result<(), Box> { - let executor = DefaultExecutor::new(); - let mut meta = ObjectMeta::default(); - meta.name = Some(resource_args.name.clone()); - meta.namespace = Some(resource_args.namespace.clone()); - meta.labels = Some(BTreeMap::from([ - ("skate.io/name".to_string(), resource_args.name), - ("skate.io/namespace".to_string(), resource_args.namespace), - ])); - - executor.manifest_delete(CronJob(K8sCronJob { - metadata: meta, - spec: None, - status: None, - }), delete_args.termination_grace_period) -} - -pub fn delete_stdin(args: DeleteArgs) -> Result<(), Box> { - let manifest = { - let mut stdin = io::stdin(); - let mut buffer = String::new(); - stdin.read_to_string(&mut buffer)?; - buffer - }; - - let executor = DefaultExecutor::new(); - let object: SupportedResources = serde_yaml::from_str(&manifest).expect("failed to deserialize manifest"); - executor.manifest_delete(object, args.termination_grace_period) -} diff --git a/src/skatelet/delete.rs b/src/skatelet/delete.rs new file mode 100644 index 0000000..1057fee --- /dev/null +++ b/src/skatelet/delete.rs @@ -0,0 +1,95 @@ +use std::collections::BTreeMap; +use std::error::Error; +use std::io; +use std::io::Read; +use clap::{Args, Subcommand}; +use crate::executor::{DefaultExecutor, Executor}; +use crate::skate::SupportedResources; +use crate::skate::SupportedResources::{CronJob, Ingress}; +use crate::skatelet::apply::StdinCommand; + +use k8s_openapi::api::batch::v1::CronJob as K8sCronJob; + +use k8s_openapi::api::networking::v1::Ingress as K8sIngress; +use k8s_openapi::apimachinery::pkg::apis::meta::v1::ObjectMeta; + +#[derive(Debug, Args, Clone)] +pub struct DeleteResourceArgs { + #[arg(long, long_help = "Name of the resource.")] + pub name: String, + #[arg(long, long_help = "Name of the resource.")] + pub namespace: String, +} + +#[derive(Debug, Subcommand, Clone)] +pub enum DeleteResourceCommands { + #[command(flatten)] + StdinCommand(StdinCommand), + Ingress(DeleteResourceArgs), + Cronjob(DeleteResourceArgs), + Secret(DeleteResourceArgs), +} + +#[derive(Debug, Args, Clone)] +pub struct DeleteArgs { + #[arg(short, long, long_help("Number of seconds to wait before hard killing."))] + termination_grace_period: Option, + #[command(subcommand)] + command: DeleteResourceCommands, +} + +pub fn delete(args: DeleteArgs) -> Result<(), Box> { + match &args.command { + DeleteResourceCommands::Ingress(resource_args) => delete_ingress(args.clone(), resource_args.clone()), + DeleteResourceCommands::StdinCommand(_) => delete_stdin(args), + DeleteResourceCommands::Cronjob(resource_args) => delete_cronjob(args.clone(), resource_args.clone()), + } +} + + +pub fn delete_ingress(delete_args: DeleteArgs, resource_args: DeleteResourceArgs) -> Result<(), Box> { + let executor = DefaultExecutor::new(); + let mut meta = ObjectMeta::default(); + meta.name = Some(resource_args.name.clone()); + meta.namespace = Some(resource_args.namespace.clone()); + meta.labels = Some(BTreeMap::from([ + ("skate.io/name".to_string(), resource_args.name), + ("skate.io/namespace".to_string(), resource_args.namespace), + ])); + + executor.manifest_delete(Ingress(K8sIngress { + metadata: meta, + spec: None, + status: None, + }), delete_args.termination_grace_period) +} + +pub fn delete_cronjob(delete_args: DeleteArgs, resource_args: DeleteResourceArgs) -> Result<(), Box> { + let executor = DefaultExecutor::new(); + let mut meta = ObjectMeta::default(); + meta.name = Some(resource_args.name.clone()); + meta.namespace = Some(resource_args.namespace.clone()); + meta.labels = Some(BTreeMap::from([ + ("skate.io/name".to_string(), resource_args.name), + ("skate.io/namespace".to_string(), resource_args.namespace), + ])); + + executor.manifest_delete(CronJob(K8sCronJob { + metadata: meta, + spec: None, + status: None, + }), delete_args.termination_grace_period) +} + +pub fn delete_stdin(args: DeleteArgs) -> Result<(), Box> { + let manifest = { + let mut stdin = io::stdin(); + let mut buffer = String::new(); + stdin.read_to_string(&mut buffer)?; + buffer + }; + + let executor = DefaultExecutor::new(); + let object: SupportedResources = serde_yaml::from_str(&manifest).expect("failed to deserialize manifest"); + executor.manifest_delete(object, args.termination_grace_period) +} diff --git a/src/skatelet/mod.rs b/src/skatelet/mod.rs index 02d0f21..3d98785 100644 --- a/src/skatelet/mod.rs +++ b/src/skatelet/mod.rs @@ -4,6 +4,7 @@ mod apply; mod system; mod cni; mod template; +mod delete; pub use skatelet::skatelet; pub use system::SystemInfo; diff --git a/src/state/state.rs b/src/state/state.rs index a74aaf2..937dda9 100644 --- a/src/state/state.rs +++ b/src/state/state.rs @@ -176,8 +176,9 @@ impl ClusterState { pub fn reconcile_object_creation(&mut self, object: &SupportedResources, node_name: &str) -> Result> { match object { SupportedResources::Pod(pod) => self.reconcile_pod_creation(&PodmanPodInfo::from((*pod).clone()), node_name), - SupportedResources::Ingress(_) => Ok(ReconciledResult { removed: 0, added: 0, updated: 1 }), // TODO - SupportedResources::CronJob(_) => Ok(ReconciledResult { removed: 0, added: 0, updated: 1 }), // TODO + SupportedResources::Ingress(_) => Ok(ReconciledResult { removed: 1, added: 1, updated: 0 }), // TODO + SupportedResources::CronJob(_) => Ok(ReconciledResult { removed: 1, added: 1, updated: 0 }), // TODO + SupportedResources::Secret(_) => Ok(ReconciledResult { removed: 1, added: 1, updated: 0 }), // TODO _ => todo!("reconcile not supported") } }