use chrono::{offset::Utc, DateTime};
use clap::Command as ClapCommand;
use comfy_table::{Cell, Table};
use k8s_openapi::ListOptional;
use k8s_openapi::{api::core::v1 as api, http::Request, List};
use rustyline::completion::Pair as RustlinePair;
use crate::command::format_duration;
use crate::{
command::command_def::{exec_match, start_clap, Cmd},
command::time_since,
completer,
env::{Env, ObjectSelection},
error::ClickError,
kobj::KObj,
output::ClickWriter,
};
use std::cell::RefCell;
use std::cmp;
use std::collections::HashMap;
use std::io::Write;
fn get_event_ts(event: &api::Event) -> Option<DateTime<Utc>> {
match &event.last_timestamp {
Some(ts) => Some(ts.0),
None => event.event_time.as_ref().map(|ts| ts.0),
}
}
fn event_cmp(e1: &api::Event, e2: &api::Event) -> cmp::Ordering {
match (get_event_ts(e1), get_event_ts(e2)) {
(None, None) => cmp::Ordering::Equal,
(None, Some(_)) => cmp::Ordering::Less,
(Some(_), None) => cmp::Ordering::Greater,
(Some(e1ts), Some(e2ts)) => e1ts.partial_cmp(&e2ts).unwrap(),
}
}
pub fn print_events_for_obj(
obj: &KObj,
env: &Env,
writer: &mut ClickWriter,
) -> Result<(), ClickError> {
let mut opts: ListOptional = Default::default();
let mut include_namespace = false;
let (request, _body) = if let Some(ns) = obj.namespace.as_ref() {
let fs = format!(
"involvedObject.name={},involvedObject.namespace={}",
obj.name(),
ns
);
opts.field_selector = Some(&fs);
api::Event::list_namespaced_event(ns, opts)?
} else {
include_namespace = true;
let fs = format!("involvedObject.name={}", obj.name(),);
opts.field_selector = Some(&fs);
api::Event::list_event_for_all_namespaces(opts)?
};
print_events(request, env, writer, include_namespace, false)
}
fn print_events_no_obj(env: &Env, writer: &mut ClickWriter) -> Result<(), ClickError> {
let mut opts: ListOptional = Default::default();
let mut include_namespace = false;
let (request, _body) = if let Some(ns) = env.namespace.as_ref() {
let fs = format!("involvedObject.namespace={}", ns);
opts.field_selector = Some(&fs);
api::Event::list_namespaced_event(ns, opts)?
} else {
include_namespace = true;
api::Event::list_event_for_all_namespaces(opts)?
};
print_events(request, env, writer, include_namespace, true)
}
fn print_events(
request: Request<Vec<u8>>,
env: &Env,
writer: &mut ClickWriter,
include_namespace: bool,
include_object: bool,
) -> Result<(), ClickError> {
let mut event_list: List<api::Event> = env.run_on_context(|c| c.execute_list(request))?;
if !event_list.items.is_empty() {
event_list.items.sort_by(event_cmp);
let mut table = Table::new();
let mut titles = if include_namespace {
vec!["Namespace"]
} else {
vec![]
};
titles.push("Last Seen");
titles.push("Type");
titles.push("Reason");
if include_object {
titles.push("Object");
}
titles.push("Message");
table.set_header(titles);
for event in event_list.items.iter() {
let mut row: Vec<Cell> = Vec::new();
if include_namespace {
row.push(Cell::new(
event.metadata.namespace.as_deref().unwrap_or("unknown"),
));
}
let timestr = match get_event_ts(event) {
Some(ts) => format_duration(time_since(ts)),
None => "unknown".to_string(),
};
row.push(Cell::new(×tr));
row.push(Cell::new(event.type_.as_deref().unwrap_or("unknown")));
row.push(Cell::new(event.reason.as_deref().unwrap_or("unknown")));
if include_object {
row.push(Cell::new(
event.involved_object.name.as_deref().unwrap_or("unknown"),
));
}
row.push(Cell::new(event.message.as_deref().unwrap_or("<none>")));
table.add_row(row);
}
crate::table::print_filled_table(&mut table, writer);
} else {
clickwriteln!(writer, "No events");
}
Ok(())
}
command!(
Events,
"events",
"Get events for the active pod",
crate::command::command_def::identity,
vec!["events"],
noop_complete!(),
no_named_complete!(),
|_matches, env, writer| {
if let ObjectSelection::None = env.current_selection() {
print_events_no_obj(env, writer)
} else {
env.apply_to_selection(
writer,
Some(&env.click_config.range_separator),
|obj, writer| print_events_for_obj(obj, env, writer),
)
}
}
);