Skip to content

Commit

Permalink
Merge pull request #316 from shadowwolf899/main
Browse files Browse the repository at this point in the history
Add ingress support
  • Loading branch information
deepu105 committed Jun 30, 2023
2 parents 8336246 + ea1b17b commit a975a48
Show file tree
Hide file tree
Showing 7 changed files with 399 additions and 7 deletions.
196 changes: 196 additions & 0 deletions src/app/ingress.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
use k8s_openapi::{
api::networking::v1::{Ingress, IngressBackend, IngressRule, IngressStatus},
chrono::Utc,
};

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

#[derive(Clone, Debug, PartialEq)]
pub struct KubeIngress {
pub namespace: String,
pub name: String,
pub ingress_class: String,
pub address: String,
pub paths: String,
pub default_backend: String,
pub age: String,
k8s_obj: Ingress,
}

impl From<Ingress> for KubeIngress {
fn from(ingress: Ingress) -> Self {
let (ingress_class, rules, default_backend) = match &ingress.spec {
Some(spec) => {
let class_name = match &spec.ingress_class_name {
Some(c) => c.clone(),
None => UNKNOWN.into(),
};
(
class_name,
get_rules(&spec.rules),
format_backend(&spec.default_backend),
)
}
None => (String::default(), None, String::default()),
};
let name = match &ingress.metadata.name {
Some(n) => n.clone(),
None => UNKNOWN.into(),
};
let namespace = match &ingress.metadata.namespace {
Some(n) => n.clone(),
None => UNKNOWN.into(),
};
let paths = match rules {
Some(r) => r,
None => String::default(),
};
Self {
name,
namespace,
ingress_class,
address: get_addresses(&ingress.status),
paths,
default_backend,
age: utils::to_age(ingress.metadata.creation_timestamp.as_ref(), Utc::now()),
k8s_obj: utils::sanitize_obj(ingress),
}
}
}

impl KubeResource<Ingress> for KubeIngress {
fn get_k8s_obj(&self) -> &Ingress {
&self.k8s_obj
}
}

fn format_backend(backend: &Option<IngressBackend>) -> String {
match backend {
Some(backend) => {
if let Some(resource) = &backend.resource {
return resource.name.to_string();
}
if let Some(service) = &backend.service {
match &service.port {
Some(port) => {
if let Some(name) = &port.name {
format!("{}:{}", service.name, name)
} else if let Some(number) = &port.number {
return format!("{}:{}", service.name, number);
} else {
return String::default();
}
}
None => String::default(),
}
} else {
String::default()
}
}
None => String::default(),
}
}

fn get_rules(i_rules: &Option<Vec<IngressRule>>) -> Option<String> {
i_rules.as_ref().map(|rules| {
rules
.iter()
.map(|i_rule| {
let mut rule = i_rule.host.clone().unwrap_or("*".to_string());
if let Some(http) = &i_rule.http {
http.paths.iter().for_each(|path| {
rule = format!(
"{}{}►{}",
rule,
&path.path.clone().unwrap_or("/*".to_string()),
format_backend(&Some(path.backend.clone()))
);
});
}
rule
})
.collect::<Vec<_>>()
.join(" ")
})
}

fn get_addresses(i_status: &Option<IngressStatus>) -> String {
match i_status {
Some(status) => match &status.load_balancer {
Some(lb) => match &lb.ingress {
Some(ingress) => ingress
.iter()
.map(|i| {
if let Some(h) = &i.hostname {
h.to_string()
} else if let Some(ip) = &i.ip {
ip.to_string()
} else {
"<pending>".to_string()
}
})
.collect::<Vec<_>>()
.join(" "),
None => String::default(),
},
None => String::default(),
},
None => String::default(),
}
}

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

#[test]
fn test_ingresses_from_api() {
let (ingresses, ingress_list): (Vec<KubeIngress>, Vec<_>) =
convert_resource_from_file("ingress");

assert_eq!(ingresses.len(), 3);
assert_eq!(
ingresses[0],
KubeIngress {
name: "ingdefault".into(),
namespace: "default".into(),
age: utils::to_age(Some(&get_time("2023-05-24T16:14:32Z")), Utc::now()),
k8s_obj: ingress_list[0].clone(),
ingress_class: "default".into(),
address: "".into(),
paths: "foo.com/►svc:8080".into(),
default_backend: "defaultsvc:http".into(),
}
);
assert_eq!(
ingresses[1],
KubeIngress {
name: "test".into(),
namespace: "default".into(),
age: utils::to_age(Some(&get_time("2023-05-24T16:20:48Z")), Utc::now()),
k8s_obj: ingress_list[1].clone(),
ingress_class: "nginx".into(),
address: "192.168.49.2".into(),
paths: "".into(),
default_backend: "test:5701".into(),
}
);
assert_eq!(
ingresses[2],
KubeIngress {
name: "test-ingress".into(),
namespace: "dev".into(),
age: utils::to_age(Some(&get_time("2023-05-24T16:22:23Z")), Utc::now()),
k8s_obj: ingress_list[2].clone(),
ingress_class: "nginx".into(),
address: "192.168.49.2".into(),
paths: "demo.apps.mlopshub.com/►hello-service:80".into(),
default_backend: "".into(),
}
);
}
}
12 changes: 11 additions & 1 deletion src/app/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pub(crate) mod contexts;
pub(crate) mod cronjobs;
pub(crate) mod daemonsets;
pub(crate) mod deployments;
pub(crate) mod ingress;
pub(crate) mod jobs;
pub(crate) mod key_binding;
pub(crate) mod metrics;
Expand Down Expand Up @@ -31,6 +32,7 @@ use self::{
cronjobs::KubeCronJob,
daemonsets::KubeDaemonSet,
deployments::KubeDeployment,
ingress::KubeIngress,
jobs::KubeJob,
key_binding::DEFAULT_KEYBINDING,
metrics::KubeNodeMetrics,
Expand Down Expand Up @@ -78,6 +80,7 @@ pub enum ActiveBlock {
RoleBindings,
ClusterRoles,
ClusterRoleBinding,
Ingress,
More,
}

Expand Down Expand Up @@ -137,6 +140,7 @@ pub struct Data {
pub role_bindings: StatefulTable<KubeRoleBinding>,
pub cluster_roles: StatefulTable<KubeClusterRole>,
pub cluster_role_binding: StatefulTable<KubeClusterRoleBinding>,
pub ingress: StatefulTable<KubeIngress>,
}

/// selected data items
Expand Down Expand Up @@ -214,6 +218,7 @@ impl Default for Data {
role_bindings: StatefulTable::new(),
cluster_roles: StatefulTable::new(),
cluster_role_binding: StatefulTable::new(),
ingress: StatefulTable::new(),
}
}
}
Expand Down Expand Up @@ -346,7 +351,7 @@ impl Default for App {
ActiveBlock::ClusterRoleBinding,
),
// ("Service Accounts".into(), ActiveBlock::RplCtrl),
// ("Ingresses".into(), ActiveBlock::RplCtrl),
("Ingresses".into(), ActiveBlock::Ingress),
// ("Network Policies".into(), ActiveBlock::RplCtrl),
]),
show_info_bar: true,
Expand Down Expand Up @@ -540,6 +545,7 @@ impl App {
self.dispatch(IoEvent::GetRoleBindings).await;
self.dispatch(IoEvent::GetClusterRoles).await;
self.dispatch(IoEvent::GetClusterRoleBinding).await;
self.dispatch(IoEvent::GetIngress).await;
self.dispatch(IoEvent::GetMetrics).await;
}

Expand Down Expand Up @@ -593,6 +599,9 @@ impl App {
ActiveBlock::ClusterRoleBinding => {
self.dispatch(IoEvent::GetClusterRoleBinding).await;
}
ActiveBlock::Ingress => {
self.dispatch(IoEvent::GetIngress).await;
}
ActiveBlock::Logs => {
if !self.is_streaming {
// do not tail to avoid duplicates
Expand Down Expand Up @@ -755,6 +764,7 @@ mod tests {
sync_io_rx.recv().await.unwrap(),
IoEvent::GetClusterRoleBinding
);
assert_eq!(sync_io_rx.recv().await.unwrap(), IoEvent::GetIngress);
assert_eq!(sync_io_rx.recv().await.unwrap(), IoEvent::GetMetrics);
assert_eq!(sync_io_rx.recv().await.unwrap(), IoEvent::GetNamespaces);
assert_eq!(sync_io_rx.recv().await.unwrap(), IoEvent::GetNodes);
Expand Down
16 changes: 16 additions & 0 deletions src/handlers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,21 @@ async fn handle_route_events(key: Key, app: &mut App) {
.await;
}
}
ActiveBlock::Ingress => {
if let Some(res) = handle_block_action(key, &mut app.data.ingress) {
let _ok = handle_describe_decode_or_yaml_action(
key,
app,
&res,
IoCmdEvent::GetDescribe {
kind: "ingress".to_owned(),
value: res.name.to_owned(),
ns: None,
},
)
.await;
}
}
ActiveBlock::Contexts | ActiveBlock::Utilization | ActiveBlock::Help => { /* Do nothing */ }
}
}
Expand Down Expand Up @@ -588,6 +603,7 @@ async fn handle_block_scroll(app: &mut App, up: bool, is_mouse: bool, page: bool
ActiveBlock::RoleBindings => app.data.role_bindings.handle_scroll(up, page),
ActiveBlock::ClusterRoles => app.data.cluster_roles.handle_scroll(up, page),
ActiveBlock::ClusterRoleBinding => app.data.cluster_role_binding.handle_scroll(up, page),
ActiveBlock::Ingress => app.data.ingress.handle_scroll(up, page),
ActiveBlock::Contexts => app.data.contexts.handle_scroll(up, page),
ActiveBlock::Utilization => app.data.metrics.handle_scroll(up, page),
ActiveBlock::Help => app.help_docs.handle_scroll(up, page),
Expand Down
21 changes: 15 additions & 6 deletions src/network/kube_api.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
use std::fmt;

use anyhow::anyhow;
use k8s_openapi::api::apps::v1::{DaemonSet, Deployment, ReplicaSet, StatefulSet};
use k8s_openapi::api::batch::v1::{CronJob, Job};
use k8s_openapi::api::core::v1::{
ConfigMap, Namespace, Node, Pod, ReplicationController, Secret, Service,
use k8s_openapi::api::{
apps::v1::{DaemonSet, Deployment, ReplicaSet, StatefulSet},
batch::v1::{CronJob, Job},
core::v1::{ConfigMap, Namespace, Node, Pod, ReplicationController, Secret, Service},
networking::v1::Ingress,
rbac::v1::{ClusterRole, ClusterRoleBinding, Role, RoleBinding},
storage::v1::StorageClass,
};
use k8s_openapi::api::rbac::v1::{ClusterRole, ClusterRoleBinding, Role, RoleBinding};
use k8s_openapi::api::storage::v1::StorageClass;
use kube::{
api::{ListMeta, ListParams, ObjectList},
config::Kubeconfig,
Expand All @@ -26,6 +27,7 @@ use crate::app::{
cronjobs::KubeCronJob,
daemonsets::KubeDaemonSet,
deployments::KubeDeployment,
ingress::KubeIngress,
jobs::KubeJob,
metrics::{self, KubeNodeMetrics},
nodes::KubeNode,
Expand Down Expand Up @@ -322,6 +324,13 @@ impl<'a> Network<'a> {
app.data.cluster_role_binding.set_items(items);
}

pub async fn get_ingress(&self) {
let items: Vec<KubeIngress> = self.get_namespaced_resources(Ingress::into).await;

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

/// calls the kubernetes API to list the given resource for either selected namespace or all namespaces
async fn get_namespaced_resources<K: ApiResource, T, F>(&self, map_fn: F) -> Vec<T>
where
Expand Down
4 changes: 4 additions & 0 deletions src/network/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ pub enum IoEvent {
GetRoleBindings,
GetClusterRoles,
GetClusterRoleBinding,
GetIngress,
GetMetrics,
RefreshClient,
}
Expand Down Expand Up @@ -174,6 +175,9 @@ impl<'a> Network<'a> {
IoEvent::GetClusterRoleBinding => {
self.get_cluster_role_binding().await;
}
IoEvent::GetIngress => {
self.get_ingress().await;
}
};

let mut app = self.app.lock().await;
Expand Down
Loading

0 comments on commit a975a48

Please sign in to comment.