use chrono::offset::Utc;
use chrono::{DateTime, Duration};
use clap::ArgMatches;
use humantime::parse_duration;
use k8s_openapi::{
apimachinery::pkg::apis::meta::v1::ObjectMeta,
http::{self, Request},
List, ListOptional, ListResponse, ListableResource, Metadata, RequestError, ResponseBody,
};
use regex::Regex;
use serde::Deserialize;
use crate::env::Env;
use crate::error::ClickError;
use crate::kobj::KObj;
use crate::output::ClickWriter;
use crate::table::CellSpec;
use std::borrow::Cow;
use std::collections::{HashMap, HashSet};
use std::fmt::Debug;
use std::io::{stderr, Write};
#[macro_use]
pub mod command_def;
pub mod alias; pub mod click; pub mod configmaps; pub mod copy; pub mod crds; pub mod daemonsets; pub mod delete; pub mod deployments; pub mod describe; pub mod events; pub mod exec; pub mod jobs; pub mod logs; pub mod namespaces; pub mod nodes; pub mod pods; pub mod portforwards; pub mod replicasets; pub mod secrets; pub mod services; pub mod statefulsets; pub mod storage; pub mod volumes;
#[cfg(feature = "argorollouts")]
pub mod rollouts;
type RowSpec<'a> = Vec<CellSpec<'a>>;
type Extractor<T> = fn(&T) -> Option<CellSpec<'_>>;
fn mapped_val(key: &str, map: &[(&'static str, &'static str)]) -> Option<&'static str> {
for (map_key, val) in map.iter() {
if &key == map_key {
return Some(val);
}
}
None
}
#[allow(clippy::too_many_arguments)] pub fn run_list_command<T, F>(
matches: ArgMatches,
env: &mut Env,
writer: &mut ClickWriter,
mut cols: Vec<&str>,
request: Request<Vec<u8>>,
col_map: &[(&'static str, &'static str)],
extra_col_map: Option<&[(&'static str, &'static str)]>,
extractors: Option<&HashMap<String, Extractor<T>>>,
get_kobj: F,
) -> Result<(), ClickError>
where
T: ListableResource + Metadata<Ty = ObjectMeta> + for<'de> Deserialize<'de> + Debug,
F: Fn(&T) -> KObj,
{
let regex = match crate::table::get_regex(&matches) {
Ok(r) => r,
Err(s) => {
writeln!(stderr(), "{}", s).unwrap_or(());
return Ok(()); }
};
let list_res = env.run_on_context::<_, List<T>>(|c| c.execute_list(request));
if list_res.is_err() {
env.clear_last_objs();
}
let list = list_res?;
let mut flags: Vec<&str> = if matches.is_valid_arg("show") {
match matches.values_of("show") {
Some(v) => v.collect(),
None => vec![],
}
} else {
vec![]
};
let sort = matches.value_of("sort").map(|s| {
let colname = s.to_lowercase();
if let Some(col) = mapped_val(&colname, col_map) {
command_def::SortCol(col)
} else if let Some(ecm) = extra_col_map {
let mut func = None;
for (flag, col) in ecm.iter() {
if flag.eq(&colname) {
flags.push(flag);
func = Some(command_def::SortCol(col));
}
}
match func {
Some(f) => f,
None => panic!("Shouldn't be allowed to ask to sort by: {}", colname),
}
} else {
panic!("Shouldn't be allowed to ask to sort by: {}", colname);
}
});
if let Some(ecm) = extra_col_map {
if env.namespace.is_none() && mapped_val("namespace", ecm).is_some() {
flags.push("namespace");
}
let labels_present = if matches.is_valid_arg("labels") {
matches.is_present("labels")
} else {
false
};
command_def::add_extra_cols(&mut cols, labels_present, flags, ecm);
}
handle_list_result(
env,
writer,
cols,
list,
extractors,
regex,
sort,
matches.is_present("reverse"),
get_kobj,
)
}
pub fn uppercase_first(s: &str) -> String {
let mut cs = s.chars();
match cs.next() {
None => String::new(),
Some(f) => f.to_uppercase().collect::<String>() + cs.as_str(),
}
}
fn valid_duration(s: &str) -> Result<(), String> {
parse_duration(s).map(|_| ()).map_err(|e| e.to_string())
}
fn valid_date(s: &str) -> Result<(), String> {
DateTime::parse_from_rfc3339(s)
.map(|_| ())
.map_err(|e| e.to_string())
}
pub fn valid_u32(s: &str) -> Result<(), String> {
s.parse::<u32>().map(|_| ()).map_err(|e| e.to_string())
}
#[allow(clippy::too_many_arguments)]
pub fn handle_list_result<'a, T, F>(
env: &mut Env,
writer: &mut ClickWriter,
cols: Vec<&str>,
list: List<T>,
extractors: Option<&HashMap<String, Extractor<T>>>,
regex: Option<Regex>,
sort: Option<command_def::SortCol>,
reverse: bool,
get_kobj: F,
) -> Result<(), ClickError>
where
T: 'a + ListableResource + Metadata<Ty = ObjectMeta>,
F: Fn(&T) -> KObj,
{
let mut specs = build_specs(&cols, &list, extractors, true, regex, get_kobj);
let mut titles: Vec<&str> = vec!["####"];
titles.reserve(cols.len());
for col in cols.iter() {
titles.push(col);
}
if let Some(command_def::SortCol(colname)) = sort {
let index = cols.iter().position(|&c| c == colname);
match index {
Some(index) => {
let idx = index + 1; specs.sort_by(|a, b| a.1.get(idx).unwrap().cmp(b.1.get(idx).unwrap()));
}
None => clickwriteln!(
writer,
"Asked to sort by {}, but it's not a column in the output",
colname
),
}
}
let (kobjs, rows): (Vec<KObj>, Vec<RowSpec>) = if reverse {
specs.into_iter().rev().unzip()
} else {
specs.into_iter().unzip()
};
crate::table::print_table(titles, rows, env, writer);
env.set_last_objs(kobjs);
Ok(())
}
pub fn build_specs<'a, T, F>(
cols: &[&str],
list: &'a List<T>,
extractors: Option<&HashMap<String, Extractor<T>>>,
include_index: bool,
regex: Option<Regex>,
get_kobj: F,
) -> Vec<(KObj, RowSpec<'a>)>
where
T: 'a + ListableResource + Metadata<Ty = ObjectMeta>,
F: Fn(&T) -> KObj,
{
let mut ret = vec![];
for item in list.items.iter() {
let mut row: Vec<CellSpec> = if include_index {
vec![CellSpec::new_index()]
} else {
vec![]
};
for col in cols.iter() {
match *col {
"Age" => row.push(extract_age(item).into()),
"Labels" => row.push(extract_labels(item).into()),
"Name" => row.push(extract_name(item).into()),
"Namespace" => row.push(extract_namespace(item).into()),
_ => match extractors {
Some(extractors) => match extractors.get(*col) {
Some(extractor) => row.push(extractor(item).into()),
None => panic!("Can't extract"),
},
None => panic!("Can't extract"),
},
}
}
match regex {
Some(ref regex) => {
if row_matches(&row, regex) {
ret.push((get_kobj(item), row));
}
}
None => {
ret.push((get_kobj(item), row));
}
}
}
ret
}
pub fn extract_name<T: Metadata<Ty = ObjectMeta>>(obj: &T) -> Option<Cow<'_, str>> {
let meta = obj.metadata();
meta.name.as_ref().map(|n| n.into())
}
pub fn extract_age<T: Metadata<Ty = ObjectMeta>>(obj: &T) -> Option<CellSpec<'_>> {
let meta = obj.metadata();
meta.creation_timestamp.as_ref().map(|ts| ts.0.into())
}
pub fn extract_namespace<T: Metadata<Ty = ObjectMeta>>(obj: &T) -> Option<Cow<'_, str>> {
let meta = obj.metadata();
meta.namespace.as_ref().map(|ns| ns.as_str().into())
}
pub fn extract_labels<T: Metadata<Ty = ObjectMeta>>(obj: &T) -> Option<Cow<'_, str>> {
let meta = obj.metadata();
meta.labels
.as_ref()
.map(|labels| keyval_string(labels.iter(), None).into())
}
fn row_matches<'a>(row: &[CellSpec<'a>], regex: &Regex) -> bool {
let mut has_match = false;
for cell_spec in row.iter() {
if !has_match {
has_match = cell_spec.matches(regex);
}
}
has_match
}
pub fn format_duration(duration: Duration) -> String {
if duration.num_days() > 365 {
let days = duration.num_days();
let yrs = days / 365;
format!("{}y {}d", yrs, (duration.num_days() - (yrs * 365)))
} else if duration.num_days() > 0 {
format!(
"{}d {}h",
duration.num_days(),
(duration.num_hours() - (24 * duration.num_days()))
)
} else if duration.num_hours() > 0 {
format!(
"{}h {}m",
duration.num_hours(),
(duration.num_minutes() - (60 * duration.num_hours()))
)
} else if duration.num_minutes() > 0 {
format!(
"{}m {}s",
duration.num_minutes(),
(duration.num_seconds() - (60 * duration.num_minutes()))
)
} else {
format!("{}s", duration.num_seconds())
}
}
pub fn time_since(date: DateTime<Utc>) -> Duration {
let now = Utc::now();
now.signed_duration_since(date)
}
pub fn keyval_string<I, K, V>(keyvals: I, skip: Option<&HashSet<String>>) -> String
where
I: Iterator<Item = (K, V)>,
K: AsRef<str>,
V: AsRef<str>,
{
let mut buf = String::new();
let mut first = true;
for (key, val) in keyvals {
if let Some(skip_set) = skip {
if skip_set.contains(key.as_ref()) {
continue;
}
}
if first {
first = false;
} else {
buf.push('\n');
}
buf.push_str(key.as_ref());
buf.push('=');
buf.push_str(val.as_ref());
}
if first {
buf.push_str("<none>");
}
buf
}
#[allow(dead_code, clippy::type_complexity)]
pub fn get_read_request_for_url<T: k8s_openapi::Response>(
url: String,
) -> Result<(Request<Vec<u8>>, fn(_: http::StatusCode) -> ResponseBody<T>), RequestError> {
let request = http::Request::get(url);
let body = vec![];
match request.body(body) {
Ok(request) => Ok((request, ResponseBody::new)),
Err(err) => Err(RequestError::Http(err)),
}
}
#[allow(dead_code, clippy::type_complexity)]
pub fn get_list_request_for_url<T: ListableResource + for<'de> serde::Deserialize<'de>>(
url: String,
optional: ListOptional<'_>,
) -> Result<
(
Request<Vec<u8>>,
fn(_: http::StatusCode) -> ResponseBody<ListResponse<T>>,
),
RequestError,
> {
let mut query_pairs = url::form_urlencoded::Serializer::new(url);
optional.__serialize(&mut query_pairs);
let __url = query_pairs.finish();
let __request = Request::get(__url);
let __body = vec![];
match __request.body(__body) {
Ok(request) => Ok((request, ResponseBody::new)),
Err(err) => Err(RequestError::Http(err)),
}
}