Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for CronJobs #69

Merged
merged 6 commits into from
Jul 6, 2021
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# -----------------------------
# Buil Kdash base image
# Build Kdash base image
# -----------------------------

FROM rust as builder
Expand All @@ -22,7 +22,6 @@ RUN rm -r src

# Copy actual source files and Build the app binary
COPY src ./src
RUN ls -a src/app/
# due to cargo bug /~https://github.com/rust-lang/rust/issues/25289
RUN apt-get update && \
apt-get install -y pkg-config libssl-dev libxcb-composite0-dev
Expand Down
73 changes: 73 additions & 0 deletions src/app/cronjobs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
use k8s_openapi::{api::batch::v2alpha1::CronJob, chrono::Utc};

use super::{models::KubeResource, utils};

#[derive(Clone, Debug, PartialEq)]
pub struct KubeCronJob {
pub name: String,
pub namespace: String,
pub schedule: String,
pub last_schedule: String,
pub suspend: bool,
pub active: usize,
pub age: String,
k8s_obj: CronJob,
}

impl KubeResource<CronJob> for KubeCronJob {
fn from_api(cronjob: &CronJob) -> Self {
let (last_schedule, active) = match &cronjob.status {
Some(cjs) => (
utils::to_age(cjs.last_schedule_time.as_ref(), Utc::now()),
cjs.active.len(),
),
None => ("<none>".to_string(), 0),
};

let (schedule, suspend) = match &cronjob.spec {
Some(cjs) => (cjs.schedule.clone(), cjs.suspend.unwrap_or_default()),
None => ("".to_string(), false),
};

KubeCronJob {
name: cronjob.metadata.name.clone().unwrap_or_default(),
namespace: cronjob.metadata.namespace.clone().unwrap_or_default(),
schedule,
suspend,
last_schedule,
active,
age: utils::to_age(cronjob.metadata.creation_timestamp.as_ref(), Utc::now()),
k8s_obj: cronjob.to_owned(),
}
}

fn get_k8s_obj(&self) -> &CronJob {
&self.k8s_obj
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::app::test_utils::{convert_resource_from_file, get_time};

#[test]
fn test_cronjobs_from_api() {
let (jobs, jobs_list): (Vec<KubeCronJob>, Vec<_>) = convert_resource_from_file("cronjobs");

assert_eq!(jobs.len(), 1);
assert_eq!(
jobs[0],
KubeCronJob {
name: "hello".into(),
namespace: "default".into(),
schedule: "*/1 * * * *".into(),
suspend: false,
active: 0,
last_schedule: utils::to_age(Some(&get_time("2021-07-05T09:39:00Z")), Utc::now()),
age: utils::to_age(Some(&get_time("2021-07-05T09:37:21Z")), Utc::now()),
k8s_obj: jobs_list[0].clone(),
}
);
}
}
6 changes: 2 additions & 4 deletions src/app/jobs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,8 @@ impl KubeResource<Job> for KubeJob {

#[cfg(test)]
mod tests {
use super::{
super::test_utils::{convert_resource_from_file, get_time},
*,
};
use super::*;
use crate::app::test_utils::{convert_resource_from_file, get_time};

#[test]
fn test_jobs_from_api() {
Expand Down
8 changes: 8 additions & 0 deletions src/app/key_binding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ generate_keybindings! {
jump_to_replicasets,
jump_to_deployments,
jump_to_jobs,
jump_to_cron_jobs,
cycle_group_by
}
#[derive(PartialEq, Eq, Clone, Copy, Hash, Debug)]
Expand Down Expand Up @@ -245,6 +246,13 @@ pub const DEFAULT_KEYBINDING: KeyBindings = KeyBindings {
desc: "Select jobs tab",
context: HContext::Overview,
},
jump_to_cron_jobs: KeyBinding {
key: Key::Char('9'),
alt: None,
desc: "Select cron jobs tab",
context: HContext::Overview,
},

cycle_group_by: KeyBinding {
key: Key::Char('g'),
alt: None,
Expand Down
17 changes: 15 additions & 2 deletions src/app/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub(crate) mod configmaps;
pub(crate) mod contexts;
pub(crate) mod cronjobs;
pub(crate) mod deployments;
pub(crate) mod jobs;
pub(crate) mod key_binding;
Expand All @@ -22,6 +23,7 @@ use tui::layout::Rect;
use self::{
configmaps::KubeConfigMap,
contexts::KubeContext,
cronjobs::KubeCronJob,
deployments::KubeDeployment,
jobs::KubeJob,
key_binding::DEFAULT_KEYBINDING,
Expand Down Expand Up @@ -57,6 +59,7 @@ pub enum ActiveBlock {
Contexts,
Utilization,
Jobs,
CronJobs,
}

#[derive(Clone, PartialEq, Debug)]
Expand Down Expand Up @@ -103,6 +106,7 @@ pub struct Data {
pub replica_sets: StatefulTable<KubeReplicaSet>,
pub deployments: StatefulTable<KubeDeployment>,
pub jobs: StatefulTable<KubeJob>,
pub cronjobs: StatefulTable<KubeCronJob>,
pub logs: LogsState,
pub describe_out: ScrollableTxt,
pub metrics: StatefulTable<(Vec<String>, Option<QtyByQualifier>)>,
Expand Down Expand Up @@ -164,6 +168,7 @@ impl Default for Data {
replica_sets: StatefulTable::new(),
deployments: StatefulTable::new(),
jobs: StatefulTable::new(),
cronjobs: StatefulTable::new(),
selected: Selected {
ns: None,
pod: None,
Expand Down Expand Up @@ -275,6 +280,13 @@ impl Default for App {
id: RouteId::Home,
},
},
TabRoute {
title: format!("Cron Jobs {}", DEFAULT_KEYBINDING.jump_to_cron_jobs.key),
route: Route {
active_block: ActiveBlock::CronJobs,
id: RouteId::Home,
},
},
]),
show_info_bar: true,
is_loading: false,
Expand Down Expand Up @@ -457,6 +469,7 @@ impl App {
self.dispatch(IoEvent::GetReplicaSets).await;
self.dispatch(IoEvent::GetDeployments).await;
self.dispatch(IoEvent::GetJobs).await;
self.dispatch(IoEvent::GetCronJobs).await;
self.refresh = false;
}
// make network requests only in intervals to avoid hogging up the network
Expand Down Expand Up @@ -556,8 +569,7 @@ mod test_utils {
assert_ne!(yaml, "".to_string());

let res_list: serde_yaml::Result<ObjectList<K>> = serde_yaml::from_str(&*yaml);
assert!(res_list.is_ok());

assert!(res_list.is_ok(), "{:?}", res_list.err());
res_list.unwrap()
}

Expand Down Expand Up @@ -607,6 +619,7 @@ mod tests {
assert_eq!(sync_io_rx.recv().await.unwrap(), IoEvent::GetReplicaSets);
assert_eq!(sync_io_rx.recv().await.unwrap(), IoEvent::GetDeployments);
assert_eq!(sync_io_rx.recv().await.unwrap(), IoEvent::GetJobs);
assert_eq!(sync_io_rx.recv().await.unwrap(), IoEvent::GetCronJobs);
assert_eq!(sync_io_rx.recv().await.unwrap(), IoEvent::GetNamespaces);
assert_eq!(sync_io_rx.recv().await.unwrap(), IoEvent::GetNodes);
assert_eq!(sync_io_rx.recv().await.unwrap(), IoEvent::GetPods);
Expand Down
20 changes: 20 additions & 0 deletions src/handlers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,10 @@ async fn handle_route_events(key: Key, app: &mut App) {
let route = app.context_tabs.set_index(7).route.clone();
app.push_navigation_route(route);
}
_ if key == DEFAULT_KEYBINDING.jump_to_cron_jobs.key => {
let route = app.context_tabs.set_index(8).route.clone();
app.push_navigation_route(route);
}
_ => {}
};

Expand Down Expand Up @@ -319,6 +323,21 @@ async fn handle_route_events(key: Key, app: &mut App) {
.await;
}
}
ActiveBlock::CronJobs => {
if let Some(res) = handle_table_action(key, &mut app.data.cronjobs) {
let _ok = handle_describe_or_yaml_action(
key,
app,
&res,
IoCmdEvent::GetDescribe {
kind: "cronjob".to_owned(),
value: res.name.to_owned(),
ns: Some(res.namespace.to_owned()),
},
)
.await;
}
}
ActiveBlock::Contexts | ActiveBlock::Utilization | ActiveBlock::Help => { /* Do nothing */ }
}
}
Expand Down Expand Up @@ -393,6 +412,7 @@ async fn handle_scroll(app: &mut App, down: bool, is_mouse: bool) {
ActiveBlock::ReplicaSets => handle_table_scroll(&mut app.data.replica_sets, down),
ActiveBlock::Deployments => handle_table_scroll(&mut app.data.deployments, down),
ActiveBlock::Jobs => handle_table_scroll(&mut app.help_docs, down),
ActiveBlock::CronJobs => handle_table_scroll(&mut app.help_docs, down),
ActiveBlock::Contexts => handle_table_scroll(&mut app.data.contexts, down),
ActiveBlock::Utilization => handle_table_scroll(&mut app.data.metrics, down),
ActiveBlock::Logs => {
Expand Down
10 changes: 10 additions & 0 deletions src/network/kube_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use super::{
super::app::{
configmaps::KubeConfigMap,
contexts,
cronjobs::KubeCronJob,
deployments::KubeDeployment,
jobs::KubeJob,
metrics::{self, KubeNodeMetrics},
Expand Down Expand Up @@ -256,6 +257,15 @@ impl<'a> Network<'a> {
app.data.jobs.set_items(items);
}

pub async fn get_cron_jobs(&self) {
let items: Vec<KubeCronJob> = self
.get_namespaced_resources(|it| KubeCronJob::from_api(it))
.await;

let mut app = self.app.lock().await;
app.data.cronjobs.set_items(items);
}

pub async fn get_deployments(&self) {
let items: Vec<KubeDeployment> = self
.get_namespaced_resources(|it| KubeDeployment::from_api(it))
Expand Down
4 changes: 4 additions & 0 deletions src/network/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ pub enum IoEvent {
GetReplicaSets,
GetDeployments,
GetJobs,
GetCronJobs,
GetMetrics,
RefreshClient,
}
Expand Down Expand Up @@ -132,6 +133,9 @@ impl<'a> Network<'a> {
IoEvent::GetJobs => {
self.get_jobs().await;
}
IoEvent::GetCronJobs => {
self.get_cron_jobs().await;
}
IoEvent::GetDeployments => {
self.get_deployments().await;
}
Expand Down
72 changes: 72 additions & 0 deletions src/ui/overview.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ static STFS_TITLE: &str = "StatefulSets";
static REPLICA_SETS_TITLE: &str = "ReplicaSets";
static DEPLOYMENTS_TITLE: &str = "Deployments";
static JOBS_TITLE: &str = "Jobs";
static CRON_JOBS_TITLE: &str = "Cron Jobs";
static DESCRIBE_ACTIVE: &str = "-> Describe ";
static YAML_ACTIVE: &str = "-> YAML ";

Expand Down Expand Up @@ -138,6 +139,7 @@ fn draw_resource_tabs_block<B: Backend>(f: &mut Frame<B>, app: &mut App, area: R
5 => draw_replica_sets_tab(app.get_current_route().active_block, f, app, chunks[1]),
6 => draw_deployments_tab(app.get_current_route().active_block, f, app, chunks[1]),
7 => draw_jobs_tab(app.get_current_route().active_block, f, app, chunks[1]),
8 => draw_cronjobs_tab(app.get_current_route().active_block, f, app, chunks[1]),
_ => {}
};
}
Expand Down Expand Up @@ -353,6 +355,30 @@ fn draw_jobs_tab<B: Backend>(block: ActiveBlock, f: &mut Frame<B>, app: &mut App
};
}

fn draw_cronjobs_tab<B: Backend>(block: ActiveBlock, f: &mut Frame<B>, app: &mut App, area: Rect) {
match block {
ActiveBlock::Describe | ActiveBlock::Yaml => draw_describe_block(
f,
app,
area,
title_with_dual_style(
get_resource_title(
app,
CRON_JOBS_TITLE,
get_describe_active(block),
app.data.cronjobs.items.len(),
),
format!("{} | {} <esc>", COPY_HINT, CRON_JOBS_TITLE),
app.light_theme,
),
),
ActiveBlock::Namespaces => {
draw_cronjobs_tab(app.get_prev_route().active_block, f, app, area);
}
_ => draw_cronjobs_block(f, app, area),
};
}

fn draw_context_info_block<B: Backend>(f: &mut Frame<B>, app: &mut App, area: Rect) {
let chunks = vertical_chunks_with_margin(
vec![
Expand Down Expand Up @@ -844,6 +870,52 @@ fn draw_jobs_block<B: Backend>(f: &mut Frame<B>, app: &mut App, area: Rect) {
);
}

fn draw_cronjobs_block<B: Backend>(f: &mut Frame<B>, app: &mut App, area: Rect) {
let title = get_resource_title(app, CRON_JOBS_TITLE, "", app.data.cronjobs.items.len());

draw_resource_block(
f,
area,
ResourceTableProps {
title,
inline_help: DESCRIBE_AND_YAML_HINT.into(),
resource: &mut app.data.cronjobs,
table_headers: vec![
"Namespace",
"Name",
"Schedule",
"Last Scheduled",
"Suspend",
"Active",
"Age",
],
column_widths: vec![
Constraint::Percentage(25),
// workaround for TUI-RS issue : /~https://github.com/fdehau/tui-rs/issues/470#issuecomment-852562848
Constraint::Percentage(39),
Constraint::Percentage(15),
Constraint::Percentage(10),
Constraint::Percentage(10),
Constraint::Percentage(10),
Constraint::Percentage(10),
],
},
|c| {
Row::new(vec![
Cell::from(c.namespace.to_owned()),
Cell::from(c.name.to_owned()),
Cell::from(c.schedule.to_owned()),
Cell::from(c.last_schedule.to_string()),
Cell::from(c.suspend.to_string()),
Cell::from(c.active.to_string()),
Cell::from(c.age.to_owned()),
])
.style(style_primary())
},
app.light_theme,
app.is_loading,
);
}
fn draw_logs_block<B: Backend>(f: &mut Frame<B>, app: &mut App, area: Rect) {
let selected_container = app.data.selected.container.clone();
let container_name = selected_container.unwrap_or_default();
Expand Down
Loading