From db63fe6921fbed31123d72351da3db4169482c9f Mon Sep 17 00:00:00 2001 From: Shinu Suresh Date: Thu, 25 Aug 2022 11:52:15 +0100 Subject: [PATCH] Adding feature cluster role binding. Fixes #52 --- src/app/mod.rs | 18 ++++++++-- src/app/roles.rs | 55 +++++++++++++++++++++++++++-- src/handlers/mod.rs | 16 +++++++++ src/network/kube_api.rs | 11 +++++- src/network/mod.rs | 4 +++ src/ui/resource_tabs.rs | 56 ++++++++++++++++++++++++++++++ test_data/clusterrole_binding.yaml | 46 ++++++++++++++++++++++++ 7 files changed, 200 insertions(+), 6 deletions(-) create mode 100644 test_data/clusterrole_binding.yaml diff --git a/src/app/mod.rs b/src/app/mod.rs index 8d2834f9..daac82ee 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -40,7 +40,7 @@ use self::{ pods::{KubeContainer, KubePod}, replicasets::KubeReplicaSet, replication_controllers::KubeReplicationController, - roles::{KubeClusterRoles, KubeRoles}, + roles::{KubeClusterRoleBinding, KubeClusterRoles, KubeRoles}, secrets::KubeSecret, statefulsets::KubeStatefulSet, storageclass::KubeStorageClass, @@ -76,6 +76,7 @@ pub enum ActiveBlock { StorageClasses, Roles, ClusterRoles, + ClusterRoleBinding, More, } @@ -133,6 +134,7 @@ pub struct Data { pub storage_classes: StatefulTable, pub roles: StatefulTable, pub clusterroles: StatefulTable, + pub clusterrolebinding: StatefulTable, } /// selected data items @@ -208,6 +210,7 @@ impl Default for Data { storage_classes: StatefulTable::new(), roles: StatefulTable::new(), clusterroles: StatefulTable::new(), + clusterrolebinding: StatefulTable::new(), } } } @@ -335,7 +338,10 @@ impl Default for App { ("Roles".into(), ActiveBlock::Roles), // ("Role Bindings".into(), ActiveBlock::RplCtrl), ("Cluster Roles".into(), ActiveBlock::ClusterRoles), - // ("Cluster Role Bindings".into(), ActiveBlock::RplCtrl), + ( + "Cluster Role Bindings".into(), + ActiveBlock::ClusterRoleBinding, + ), // ("Service Accounts".into(), ActiveBlock::RplCtrl), // ("Ingresses".into(), ActiveBlock::RplCtrl), // ("Network Policies".into(), ActiveBlock::RplCtrl), @@ -531,6 +537,7 @@ impl App { self.dispatch(IoEvent::GetStorageClasses).await; self.dispatch(IoEvent::GetRoles).await; self.dispatch(IoEvent::GetClusterRoles).await; + self.dispatch(IoEvent::GetClusterRoleBinding).await; self.dispatch(IoEvent::GetMetrics).await; } @@ -578,6 +585,9 @@ impl App { ActiveBlock::ClusterRoles => { self.dispatch(IoEvent::GetClusterRoles).await; } + ActiveBlock::ClusterRoleBinding => { + self.dispatch(IoEvent::GetClusterRoleBinding).await; + } ActiveBlock::Logs => { if !self.is_streaming { // do not tail to avoid duplicates @@ -737,6 +747,10 @@ mod tests { assert_eq!(sync_io_rx.recv().await.unwrap(), IoEvent::GetStorageClasses); assert_eq!(sync_io_rx.recv().await.unwrap(), IoEvent::GetRoles); assert_eq!(sync_io_rx.recv().await.unwrap(), IoEvent::GetClusterRoles); + assert_eq!( + sync_io_rx.recv().await.unwrap(), + IoEvent::GetClusterRoleBinding + ); 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); diff --git a/src/app/roles.rs b/src/app/roles.rs index 4db1d809..56028bd7 100644 --- a/src/app/roles.rs +++ b/src/app/roles.rs @@ -1,4 +1,6 @@ -use k8s_openapi::{api::rbac::v1::ClusterRole, api::rbac::v1::Role, chrono::Utc}; +use k8s_openapi::{ + api::rbac::v1::ClusterRole, api::rbac::v1::ClusterRoleBinding, api::rbac::v1::Role, chrono::Utc, +}; use super::{models::KubeResource, utils}; @@ -17,6 +19,14 @@ pub struct KubeClusterRoles { k8s_obj: ClusterRole, } +#[derive(Clone, Debug, PartialEq)] +pub struct KubeClusterRoleBinding { + pub name: String, + pub role: String, + pub age: String, + k8s_obj: ClusterRoleBinding, +} + impl KubeResource for KubeRoles { fn from_api(role: &Role) -> Self { KubeRoles { @@ -46,9 +56,31 @@ impl KubeResource for KubeClusterRoles { } } +impl KubeResource for KubeClusterRoleBinding { + fn from_api(clusterrolebinding: &ClusterRoleBinding) -> Self { + KubeClusterRoleBinding { + name: clusterrolebinding.metadata.name.clone().unwrap_or_default(), + role: format!( + "{}/{}", + clusterrolebinding.role_ref.kind.clone(), + clusterrolebinding.role_ref.name.clone() + ), + age: utils::to_age( + clusterrolebinding.metadata.creation_timestamp.as_ref(), + Utc::now(), + ), + k8s_obj: clusterrolebinding.to_owned(), + } + } + + fn get_k8s_obj(&self) -> &ClusterRoleBinding { + &self.k8s_obj + } +} + #[cfg(test)] mod tests { - use crate::app::roles::{KubeClusterRoles, KubeRoles}; + use crate::app::roles::{KubeClusterRoleBinding, KubeClusterRoles, KubeRoles}; use crate::app::test_utils::{convert_resource_from_file, get_time}; use crate::app::utils; use k8s_openapi::chrono::Utc; @@ -70,7 +102,7 @@ mod tests { } #[test] - fn test_cluster_roles_binding_from_rbac_api() { + fn test_cluster_roles_from_rbac_api() { let (clusterroles, cluster_roles_list): (Vec, Vec<_>) = convert_resource_from_file("clusterroles"); @@ -84,4 +116,21 @@ mod tests { } ) } + + #[test] + fn test_cluster_role_bindings_from_rbac_api() { + let (clusterrolebinding, cluster_role_bindings_list): (Vec, Vec<_>) = + convert_resource_from_file("clusterrole_binding"); + + assert_eq!(clusterrolebinding.len(), 2); + assert_eq!( + clusterrolebinding[0], + KubeClusterRoleBinding { + name: "admin-user".into(), + role: "ClusterRole/cluster-admin".into(), + age: utils::to_age(Some(&get_time("2022-03-02T16:50:53Z")), Utc::now()), + k8s_obj: cluster_role_bindings_list[0].clone(), + } + ) + } } diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs index 444ec9a4..dab89430 100644 --- a/src/handlers/mod.rs +++ b/src/handlers/mod.rs @@ -490,6 +490,21 @@ async fn handle_route_events(key: Key, app: &mut App) { .await; } } + ActiveBlock::ClusterRoleBinding => { + if let Some(res) = handle_block_action(key, &mut app.data.clusterrolebinding) { + let _ok = handle_describe_decode_or_yaml_action( + key, + app, + &res, + IoCmdEvent::GetDescribe { + kind: "clusterrolebinding".to_owned(), + value: res.name.to_owned(), + ns: None, + }, + ) + .await; + } + } ActiveBlock::Contexts | ActiveBlock::Utilization | ActiveBlock::Help => { /* Do nothing */ } } } @@ -555,6 +570,7 @@ async fn handle_block_scroll(app: &mut App, up: bool, is_mouse: bool, page: bool ActiveBlock::StorageClasses => app.data.storage_classes.handle_scroll(up, page), ActiveBlock::Roles => app.data.roles.handle_scroll(up, page), ActiveBlock::ClusterRoles => app.data.clusterroles.handle_scroll(up, page), + ActiveBlock::ClusterRoleBinding => app.data.clusterrolebinding.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), diff --git a/src/network/kube_api.rs b/src/network/kube_api.rs index dbc82a5d..e4e97487 100644 --- a/src/network/kube_api.rs +++ b/src/network/kube_api.rs @@ -28,7 +28,7 @@ use crate::app::{ pods::KubePod, replicasets::KubeReplicaSet, replication_controllers::KubeReplicationController, - roles::{KubeClusterRoles, KubeRoles}, + roles::{KubeClusterRoleBinding, KubeClusterRoles, KubeRoles}, secrets::KubeSecret, statefulsets::KubeStatefulSet, storageclass::KubeStorageClass, @@ -327,6 +327,15 @@ impl<'a> Network<'a> { app.data.clusterroles.set_items(items); } + pub async fn get_cluster_role_binding(&self) { + let items: Vec = self + .get_namespaced_resources(|it| KubeClusterRoleBinding::from_api(it)) + .await; + + let mut app = self.app.lock().await; + app.data.clusterrolebinding.set_items(items); + } + /// calls the kubernetes API to list the given resource for either selected namespace or all namespaces async fn get_namespaced_resources(&self, map_fn: F) -> Vec where diff --git a/src/network/mod.rs b/src/network/mod.rs index 8dcc07b3..9d70fcf8 100644 --- a/src/network/mod.rs +++ b/src/network/mod.rs @@ -30,6 +30,7 @@ pub enum IoEvent { GetStorageClasses, GetRoles, GetClusterRoles, + GetClusterRoleBinding, GetMetrics, RefreshClient, } @@ -166,6 +167,9 @@ impl<'a> Network<'a> { IoEvent::GetClusterRoles => { self.get_cluster_roles().await; } + IoEvent::GetClusterRoleBinding => { + self.get_cluster_role_binding().await; + } }; let mut app = self.app.lock().await; diff --git a/src/ui/resource_tabs.rs b/src/ui/resource_tabs.rs index 17ed45a3..8ce21650 100644 --- a/src/ui/resource_tabs.rs +++ b/src/ui/resource_tabs.rs @@ -37,6 +37,7 @@ static RPL_CTRL_TITLE: &str = "ReplicationControllers"; static STORAGE_CLASSES_LABEL: &str = "StorageClasses"; static ROLES_TITLE: &str = "Roles"; static CLUSTER_ROLES_TITLE: &str = "ClusterRoles"; +static CLUSTER_ROLES_BINDING_TITLE: &str = "ClusterRoleBinding"; static DESCRIBE_ACTIVE: &str = "-> Describe "; static YAML_ACTIVE: &str = "-> YAML "; @@ -88,6 +89,7 @@ fn draw_more(block: ActiveBlock, f: &mut Frame<'_, B>, app: &mut App ActiveBlock::StorageClasses => draw_storage_classes_tab(block, f, app, area), ActiveBlock::Roles => draw_roles_tab(block, f, app, area), ActiveBlock::ClusterRoles => draw_cluster_roles_tab(block, f, app, area), + ActiveBlock::ClusterRoleBinding => draw_cluster_role_binding_tab(block, f, app, area), ActiveBlock::Describe | ActiveBlock::Yaml => { let mut prev_route = app.get_prev_route(); if prev_route.active_block == block { @@ -100,6 +102,7 @@ fn draw_more(block: ActiveBlock, f: &mut Frame<'_, B>, app: &mut App ActiveBlock::StorageClasses => draw_storage_classes_tab(block, f, app, area), ActiveBlock::Roles => draw_roles_tab(block, f, app, area), ActiveBlock::ClusterRoles => draw_cluster_roles_tab(block, f, app, area), + ActiveBlock::ClusterRoleBinding => draw_cluster_role_binding_tab(block, f, app, area), _ => { /* do nothing */ } } } @@ -1110,6 +1113,59 @@ fn draw_cluster_roles_block(f: &mut Frame<'_, B>, app: &mut App, are ); } +fn draw_cluster_role_binding_tab( + block: ActiveBlock, + f: &mut Frame<'_, B>, + app: &mut App, + area: Rect, +) { + draw_resource_tab!( + CLUSTER_ROLES_BINDING_TITLE, + block, + f, + app, + area, + draw_cluster_role_binding_tab, + draw_cluster_role_binding_block, + app.data.clusterrolebinding + ); +} + +fn draw_cluster_role_binding_block(f: &mut Frame<'_, B>, app: &mut App, area: Rect) { + let title = get_resource_title( + app, + CLUSTER_ROLES_BINDING_TITLE, + "", + app.data.clusterrolebinding.items.len(), + ); + + draw_resource_block( + f, + area, + ResourceTableProps { + title, + inline_help: DESCRIBE_YAML_AND_ESC_HINT.into(), + resource: &mut app.data.clusterrolebinding, + table_headers: vec!["Name", "Role", "Age"], + column_widths: vec![ + Constraint::Percentage(40), + Constraint::Percentage(40), + Constraint::Percentage(20), + ], + }, + |c| { + Row::new(vec![ + Cell::from(c.name.to_owned()), + Cell::from(c.role.to_owned()), + Cell::from(c.age.to_owned()), + ]) + .style(style_primary(app.light_theme)) + }, + app.light_theme, + app.is_loading, + ); +} + /// common for all resources fn draw_describe_block( f: &mut Frame<'_, B>, diff --git a/test_data/clusterrole_binding.yaml b/test_data/clusterrole_binding.yaml new file mode 100644 index 00000000..d27aa9fd --- /dev/null +++ b/test_data/clusterrole_binding.yaml @@ -0,0 +1,46 @@ +apiVersion: v1 +items: + - apiVersion: rbac.authorization.k8s.io/v1 + kind: ClusterRoleBinding + metadata: + creationTimestamp: "2022-03-02T16:50:53Z" + name: admin-user + resourceVersion: "70549225" + uid: e86a4046-d74b-457e-9e93-6269a675284d + roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cluster-admin + subjects: + - kind: ServiceAccount + name: power-user + namespace: kube-system + - kind: ServiceAccount + name: admin-user + namespace: kube-system + - apiVersion: rbac.authorization.k8s.io/v1 + kind: ClusterRoleBinding + metadata: + annotations: + kubectl.kubernetes.io/last-applied-configuration: | + {"apiVersion":"rbac.authorization.k8s.io/v1","kind":"ClusterRoleBinding","metadata":{"annotations":{},"name":"aws-node"},"roleRef":{"apiGroup":"rbac.authorization.k8s.io","kind":"ClusterRole","name":"aws-node"},"subjects":[{"kind":"ServiceAccount","name":"aws-node","namespace":"kube-system"}]} + meta.helm.sh/release-name: aws-cni + meta.helm.sh/release-namespace: kube-system + creationTimestamp: "2022-03-02T16:42:18Z" + labels: + app.kubernetes.io/managed-by: Helm + name: aws-node + resourceVersion: "70549265" + uid: f6dd32c5-f853-4a8b-a98b-0726d4702cbe + roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: aws-node + subjects: + - kind: ServiceAccount + name: aws-node + namespace: kube-system +kind: List +metadata: + resourceVersion: "" + selfLink: ""