diff --git a/CHANGELOG.md b/CHANGELOG.md index dab35d52..2dd64c31 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unreleased - 2022-XX-YY - Add support for RoleBindings (https://github.com/kdash-rs/kdash/pull/245) +- Add support for ClusterRoleBindings (https://github.com/kdash-rs/kdash/pull/249) ## [0.3.4] - 2022-08-18 diff --git a/src/app/mod.rs b/src/app/mod.rs index 4a5949b5..8773dc4a 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, KubeRoleBindings, KubeRoles}, + roles::{KubeClusterRoleBinding, KubeClusterRoles, KubeRoleBindings, KubeRoles}, secrets::KubeSecret, statefulsets::KubeStatefulSet, storageclass::KubeStorageClass, @@ -77,6 +77,7 @@ pub enum ActiveBlock { Roles, RoleBindings, ClusterRoles, + ClusterRoleBinding, More, } @@ -134,7 +135,8 @@ pub struct Data { pub storage_classes: StatefulTable, pub roles: StatefulTable, pub role_bindings: StatefulTable, - pub clusterroles: StatefulTable, + pub cluster_roles: StatefulTable, + pub cluster_role_binding: StatefulTable, } /// selected data items @@ -210,7 +212,8 @@ impl Default for Data { storage_classes: StatefulTable::new(), roles: StatefulTable::new(), role_bindings: StatefulTable::new(), - clusterroles: StatefulTable::new(), + cluster_roles: StatefulTable::new(), + cluster_role_binding: StatefulTable::new(), } } } @@ -338,7 +341,10 @@ impl Default for App { ("Roles".into(), ActiveBlock::Roles), ("Role Bindings".into(), ActiveBlock::RoleBindings), ("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), @@ -535,6 +541,7 @@ impl App { self.dispatch(IoEvent::GetRoles).await; self.dispatch(IoEvent::GetRoleBindings).await; self.dispatch(IoEvent::GetClusterRoles).await; + self.dispatch(IoEvent::GetClusterRoleBinding).await; self.dispatch(IoEvent::GetMetrics).await; } @@ -585,6 +592,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 @@ -745,6 +755,10 @@ mod tests { assert_eq!(sync_io_rx.recv().await.unwrap(), IoEvent::GetRoles); assert_eq!(sync_io_rx.recv().await.unwrap(), IoEvent::GetRoleBindings); 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 215de285..854028b7 100644 --- a/src/app/roles.rs +++ b/src/app/roles.rs @@ -1,5 +1,5 @@ use k8s_openapi::{ - api::rbac::v1::{ClusterRole, Role, RoleBinding}, + api::rbac::v1::{ClusterRole, ClusterRoleBinding, Role, RoleBinding}, chrono::Utc, }; @@ -13,6 +13,15 @@ pub struct KubeRoles { k8s_obj: Role, } +#[derive(Clone, Debug, PartialEq)] +pub struct KubeRoleBindings { + pub namespace: String, + pub name: String, + pub role: String, + pub age: String, + k8s_obj: RoleBinding, +} + #[derive(Clone, Debug, PartialEq)] pub struct KubeClusterRoles { pub name: String, @@ -21,12 +30,11 @@ pub struct KubeClusterRoles { } #[derive(Clone, Debug, PartialEq)] -pub struct KubeRoleBindings { - pub namespace: String, +pub struct KubeClusterRoleBinding { pub name: String, pub role: String, pub age: String, - k8s_obj: RoleBinding, + k8s_obj: ClusterRoleBinding, } impl KubeResource for KubeRoles { @@ -45,11 +53,14 @@ impl KubeResource for KubeRoles { } impl KubeResource for KubeClusterRoles { - fn from_api(clusterrole: &ClusterRole) -> Self { + fn from_api(cluster_role: &ClusterRole) -> Self { KubeClusterRoles { - name: clusterrole.metadata.name.clone().unwrap_or_default(), - age: utils::to_age(clusterrole.metadata.creation_timestamp.as_ref(), Utc::now()), - k8s_obj: clusterrole.to_owned(), + name: cluster_role.metadata.name.clone().unwrap_or_default(), + age: utils::to_age( + cluster_role.metadata.creation_timestamp.as_ref(), + Utc::now(), + ), + k8s_obj: cluster_role.to_owned(), } } @@ -59,13 +70,16 @@ impl KubeResource for KubeClusterRoles { } impl KubeResource for KubeRoleBindings { - fn from_api(rolebinding: &RoleBinding) -> Self { + fn from_api(role_binding: &RoleBinding) -> Self { KubeRoleBindings { - namespace: rolebinding.metadata.namespace.clone().unwrap_or_default(), - name: rolebinding.metadata.name.clone().unwrap_or_default(), - role: rolebinding.role_ref.name.clone(), - age: utils::to_age(rolebinding.metadata.creation_timestamp.as_ref(), Utc::now()), - k8s_obj: rolebinding.to_owned(), + namespace: role_binding.metadata.namespace.clone().unwrap_or_default(), + name: role_binding.metadata.name.clone().unwrap_or_default(), + role: role_binding.role_ref.name.clone(), + age: utils::to_age( + role_binding.metadata.creation_timestamp.as_ref(), + Utc::now(), + ), + k8s_obj: role_binding.to_owned(), } } @@ -74,12 +88,38 @@ impl KubeResource for KubeRoleBindings { } } +impl KubeResource for KubeClusterRoleBinding { + fn from_api(cluster_role_binding: &ClusterRoleBinding) -> Self { + KubeClusterRoleBinding { + name: cluster_role_binding + .metadata + .name + .clone() + .unwrap_or_default(), + role: format!( + "{}/{}", + cluster_role_binding.role_ref.kind.clone(), + cluster_role_binding.role_ref.name.clone() + ), + age: utils::to_age( + cluster_role_binding.metadata.creation_timestamp.as_ref(), + Utc::now(), + ), + k8s_obj: cluster_role_binding.to_owned(), + } + } + + fn get_k8s_obj(&self) -> &ClusterRoleBinding { + &self.k8s_obj + } +} + #[cfg(test)] mod tests { use k8s_openapi::chrono::Utc; use crate::app::{ - roles::{KubeClusterRoles, KubeRoleBindings, KubeRoles}, + roles::{KubeClusterRoleBinding, KubeClusterRoles, KubeRoleBindings, KubeRoles}, test_utils::{convert_resource_from_file, get_time}, utils, }; @@ -101,13 +141,13 @@ mod tests { } #[test] - fn test_cluster_roles_binding_from_rbac_api() { - let (clusterroles, cluster_roles_list): (Vec, Vec<_>) = + fn test_cluster_roles_from_rbac_api() { + let (cluster_roles, cluster_roles_list): (Vec, Vec<_>) = convert_resource_from_file("clusterroles"); - assert_eq!(clusterroles.len(), 1); + assert_eq!(cluster_roles.len(), 1); assert_eq!( - clusterroles[0], + cluster_roles[0], KubeClusterRoles { name: "admin".into(), age: utils::to_age(Some(&get_time("2021-12-14T11:04:22Z")), Utc::now()), @@ -118,12 +158,12 @@ mod tests { #[test] fn test_role_binding_from_rbac_api() { - let (rolebindings, rolebindings_list): (Vec, Vec<_>) = + let (role_bindings, rolebindings_list): (Vec, Vec<_>) = convert_resource_from_file("role_bindings"); - assert_eq!(rolebindings.len(), 1); + assert_eq!(role_bindings.len(), 1); assert_eq!( - rolebindings[0], + role_bindings[0], KubeRoleBindings { namespace: "default".to_string(), name: "kiali".into(), @@ -133,4 +173,21 @@ mod tests { } ) } + + #[test] + fn test_cluster_role_bindings_from_rbac_api() { + let (cluster_role_binding, cluster_role_bindings_list): (Vec, Vec<_>) = + convert_resource_from_file("clusterrole_binding"); + + assert_eq!(cluster_role_binding.len(), 2); + assert_eq!( + cluster_role_binding[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 d09ac436..9272638f 100644 --- a/src/handlers/mod.rs +++ b/src/handlers/mod.rs @@ -491,7 +491,7 @@ async fn handle_route_events(key: Key, app: &mut App) { } } ActiveBlock::ClusterRoles => { - if let Some(res) = handle_block_action(key, &mut app.data.clusterroles) { + if let Some(res) = handle_block_action(key, &mut app.data.cluster_roles) { let _ok = handle_describe_decode_or_yaml_action( key, app, @@ -505,6 +505,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.cluster_role_binding) { + 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 */ } } } @@ -570,7 +585,8 @@ 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::RoleBindings => app.data.role_bindings.handle_scroll(up, page), - ActiveBlock::ClusterRoles => app.data.clusterroles.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::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 52aa2596..115799f8 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, KubeRoleBindings, KubeRoles}, + roles::{KubeClusterRoleBinding, KubeClusterRoles, KubeRoleBindings, KubeRoles}, secrets::KubeSecret, statefulsets::KubeStatefulSet, storageclass::KubeStorageClass, @@ -333,7 +333,16 @@ impl<'a> Network<'a> { .await; let mut app = self.app.lock().await; - app.data.clusterroles.set_items(items); + app.data.cluster_roles.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.cluster_role_binding.set_items(items); } /// calls the kubernetes API to list the given resource for either selected namespace or all namespaces diff --git a/src/network/mod.rs b/src/network/mod.rs index b55ba065..5e50fc32 100644 --- a/src/network/mod.rs +++ b/src/network/mod.rs @@ -31,6 +31,7 @@ pub enum IoEvent { GetRoles, GetRoleBindings, GetClusterRoles, + GetClusterRoleBinding, GetMetrics, RefreshClient, } @@ -170,6 +171,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 eea11c4c..56e83969 100644 --- a/src/ui/resource_tabs.rs +++ b/src/ui/resource_tabs.rs @@ -38,6 +38,7 @@ static STORAGE_CLASSES_LABEL: &str = "StorageClasses"; static ROLES_TITLE: &str = "Roles"; static ROLE_BINDINGS_TITLE: &str = "RoleBindings"; static CLUSTER_ROLES_TITLE: &str = "ClusterRoles"; +static CLUSTER_ROLES_BINDING_TITLE: &str = "ClusterRoleBinding"; static DESCRIBE_ACTIVE: &str = "-> Describe "; static YAML_ACTIVE: &str = "-> YAML "; @@ -90,6 +91,7 @@ fn draw_more(block: ActiveBlock, f: &mut Frame<'_, B>, app: &mut App ActiveBlock::Roles => draw_roles_tab(block, f, app, area), ActiveBlock::RoleBindings => draw_role_bindings_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 { @@ -103,6 +105,7 @@ fn draw_more(block: ActiveBlock, f: &mut Frame<'_, B>, app: &mut App ActiveBlock::Roles => draw_roles_tab(block, f, app, area), ActiveBlock::RoleBindings => draw_role_bindings_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 */ } } } @@ -1134,7 +1137,7 @@ fn draw_cluster_roles_tab( area, draw_cluster_roles_tab, draw_cluster_roles_block, - app.data.clusterroles + app.data.cluster_roles ); } @@ -1143,7 +1146,7 @@ fn draw_cluster_roles_block(f: &mut Frame<'_, B>, app: &mut App, are app, CLUSTER_ROLES_TITLE, "", - app.data.clusterroles.items.len(), + app.data.cluster_roles.items.len(), ); draw_resource_block( @@ -1152,7 +1155,7 @@ fn draw_cluster_roles_block(f: &mut Frame<'_, B>, app: &mut App, are ResourceTableProps { title, inline_help: DESCRIBE_YAML_AND_ESC_HINT.into(), - resource: &mut app.data.clusterroles, + resource: &mut app.data.cluster_roles, table_headers: vec!["Name", "Age"], column_widths: vec![Constraint::Percentage(50), Constraint::Percentage(50)], }, @@ -1168,6 +1171,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.cluster_role_binding + ); +} + +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.cluster_role_binding.items.len(), + ); + + draw_resource_block( + f, + area, + ResourceTableProps { + title, + inline_help: DESCRIBE_YAML_AND_ESC_HINT.into(), + resource: &mut app.data.cluster_role_binding, + 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: ""