diff --git a/README.md b/README.md index 3d5e43ad..93d49228 100644 --- a/README.md +++ b/README.md @@ -220,4 +220,4 @@ MIT - [Asha Somayajula](https://github.com/somayaj) - [Tobias de Bruijn](https://github.com/TheDutchMC) - [Omid Rad](https://github.com/omid) -- [shinu-ynap](https://github.com/shinu-ynap) +- [Shinu Suresh](https://github.com/shinusuresh) diff --git a/src/app/mod.rs b/src/app/mod.rs index c210b1d2..12e87972 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::KubeRoles, + roles::{KubeClusterRoles, KubeRoles}, secrets::KubeSecret, statefulsets::KubeStatefulSet, storageclass::KubeStorageClass, @@ -75,6 +75,7 @@ pub enum ActiveBlock { RplCtrl, StorageClasses, Roles, + ClusterRoles, More, } @@ -131,6 +132,7 @@ pub struct Data { pub rpl_ctrls: StatefulTable, pub storage_classes: StatefulTable, pub roles: StatefulTable, + pub clusterroles: StatefulTable, } /// selected data items @@ -205,6 +207,7 @@ impl Default for Data { rpl_ctrls: StatefulTable::new(), storage_classes: StatefulTable::new(), roles: StatefulTable::new(), + clusterroles: StatefulTable::new(), } } } @@ -331,7 +334,7 @@ impl Default for App { ("Storage Classes".into(), ActiveBlock::StorageClasses), ("Roles".into(), ActiveBlock::Roles), // ("Role Bindings".into(), ActiveBlock::RplCtrl), - // ("Cluster Roles".into(), ActiveBlock::RplCtrl), + ("Cluster Roles".into(), ActiveBlock::ClusterRoles), // ("Cluster Role Bindings".into(), ActiveBlock::RplCtrl), // ("Service Accounts".into(), ActiveBlock::RplCtrl), // ("Ingresses".into(), ActiveBlock::RplCtrl), @@ -527,6 +530,7 @@ impl App { self.dispatch(IoEvent::GetReplicationControllers).await; self.dispatch(IoEvent::GetStorageClasses).await; self.dispatch(IoEvent::GetRoles).await; + self.dispatch(IoEvent::GetClusterRoles).await; self.dispatch(IoEvent::GetMetrics).await; } @@ -571,6 +575,9 @@ impl App { ActiveBlock::Roles => { self.dispatch(IoEvent::GetRoles).await; } + ActiveBlock::ClusterRoles => { + self.dispatch(IoEvent::GetClusterRoles).await; + } ActiveBlock::Logs => { if !self.is_streaming { // do not tail to avoid duplicates @@ -729,6 +736,7 @@ 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::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 5da38d04..4db1d809 100644 --- a/src/app/roles.rs +++ b/src/app/roles.rs @@ -1,4 +1,4 @@ -use k8s_openapi::{api::rbac::v1::Role, chrono::Utc}; +use k8s_openapi::{api::rbac::v1::ClusterRole, api::rbac::v1::Role, chrono::Utc}; use super::{models::KubeResource, utils}; @@ -10,6 +10,13 @@ pub struct KubeRoles { k8s_obj: Role, } +#[derive(Clone, Debug, PartialEq)] +pub struct KubeClusterRoles { + pub name: String, + pub age: String, + k8s_obj: ClusterRole, +} + impl KubeResource for KubeRoles { fn from_api(role: &Role) -> Self { KubeRoles { @@ -25,9 +32,23 @@ impl KubeResource for KubeRoles { } } +impl KubeResource for KubeClusterRoles { + fn from_api(clusterrole: &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(), + } + } + + fn get_k8s_obj(&self) -> &ClusterRole { + &self.k8s_obj + } +} + #[cfg(test)] mod tests { - use crate::app::roles::KubeRoles; + use crate::app::roles::{KubeClusterRoles, KubeRoles}; use crate::app::test_utils::{convert_resource_from_file, get_time}; use crate::app::utils; use k8s_openapi::chrono::Utc; @@ -47,4 +68,20 @@ mod tests { } ) } + + #[test] + fn test_cluster_roles_binding_from_rbac_api() { + let (clusterroles, cluster_roles_list): (Vec, Vec<_>) = + convert_resource_from_file("clusterroles"); + + assert_eq!(clusterroles.len(), 1); + assert_eq!( + clusterroles[0], + KubeClusterRoles { + name: "admin".into(), + age: utils::to_age(Some(&get_time("2021-12-14T11:04:22Z")), Utc::now()), + k8s_obj: cluster_roles_list[0].clone(), + } + ) + } } diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs index 87e86f96..45b04195 100644 --- a/src/handlers/mod.rs +++ b/src/handlers/mod.rs @@ -446,6 +446,21 @@ async fn handle_route_events(key: Key, app: &mut App) { .await; } } + ActiveBlock::ClusterRoles => { + if let Some(res) = handle_block_action(key, &mut app.data.clusterroles) { + let _ok = handle_describe_or_yaml_action( + key, + app, + &res, + IoCmdEvent::GetDescribe { + kind: "clusterroles".to_owned(), + value: res.name.to_owned(), + ns: None, + }, + ) + .await; + } + } ActiveBlock::Contexts | ActiveBlock::Utilization | ActiveBlock::Help => { /* Do nothing */ } } } @@ -509,6 +524,7 @@ async fn handle_block_scroll(app: &mut App, up: bool, is_mouse: bool, page: bool ActiveBlock::RplCtrl => app.data.rpl_ctrls.handle_scroll(up, page), 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::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 2a352b3c..dbc82a5d 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::KubeRoles, + roles::{KubeClusterRoles, KubeRoles}, secrets::KubeSecret, statefulsets::KubeStatefulSet, storageclass::KubeStorageClass, @@ -318,6 +318,15 @@ impl<'a> Network<'a> { app.data.roles.set_items(items); } + pub async fn get_cluster_roles(&self) { + let items: Vec = self + .get_namespaced_resources(|it| KubeClusterRoles::from_api(it)) + .await; + + let mut app = self.app.lock().await; + app.data.clusterroles.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 979d1447..5d894fbb 100644 --- a/src/network/mod.rs +++ b/src/network/mod.rs @@ -29,6 +29,7 @@ pub enum IoEvent { GetReplicationControllers, GetStorageClasses, GetRoles, + GetClusterRoles, GetMetrics, RefreshClient, } @@ -162,6 +163,9 @@ impl<'a> Network<'a> { IoEvent::GetRoles => { self.get_roles().await; } + IoEvent::GetClusterRoles => { + self.get_cluster_roles().await; + } }; let mut app = self.app.lock().await; diff --git a/src/ui/resource_tabs.rs b/src/ui/resource_tabs.rs index 80fb48b7..ef593efa 100644 --- a/src/ui/resource_tabs.rs +++ b/src/ui/resource_tabs.rs @@ -34,6 +34,7 @@ static SECRETS_TITLE: &str = "Secrets"; static RPL_CTRL_TITLE: &str = "ReplicationControllers"; static STORAGE_CLASSES_LABEL: &str = "StorageClasses"; static ROLES_TITLE: &str = "Roles"; +static CLUSTER_ROLES_TITLE: &str = "ClusterRoles"; static DESCRIBE_ACTIVE: &str = "-> Describe "; static YAML_ACTIVE: &str = "-> YAML "; @@ -84,6 +85,7 @@ fn draw_more(block: ActiveBlock, f: &mut Frame<'_, B>, app: &mut App ActiveBlock::RplCtrl => draw_replication_controllers_tab(block, f, app, area), 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::Describe | ActiveBlock::Yaml => { let mut prev_route = app.get_prev_route(); if prev_route.active_block == block { @@ -95,6 +97,7 @@ fn draw_more(block: ActiveBlock, f: &mut Frame<'_, B>, app: &mut App ActiveBlock::RplCtrl => draw_replication_controllers_tab(block, f, app, area), 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), _ => { /* do nothing */ } } } @@ -1057,6 +1060,54 @@ fn draw_roles_block(f: &mut Frame<'_, B>, app: &mut App, area: Rect) ); } +fn draw_cluster_roles_tab( + block: ActiveBlock, + f: &mut Frame<'_, B>, + app: &mut App, + area: Rect, +) { + draw_resource_tab!( + CLUSTER_ROLES_TITLE, + block, + f, + app, + area, + draw_cluster_roles_tab, + draw_cluster_roles_block, + app.data.clusterroles + ); +} + +fn draw_cluster_roles_block(f: &mut Frame<'_, B>, app: &mut App, area: Rect) { + let title = get_resource_title( + app, + CLUSTER_ROLES_TITLE, + "", + app.data.clusterroles.items.len(), + ); + + draw_resource_block( + f, + area, + ResourceTableProps { + title, + inline_help: DESCRIBE_YAML_AND_ESC_HINT.into(), + resource: &mut app.data.clusterroles, + table_headers: vec!["Name", "Age"], + column_widths: vec![Constraint::Percentage(50), Constraint::Percentage(50)], + }, + |c| { + Row::new(vec![ + Cell::from(c.name.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/clusterroles.yaml b/test_data/clusterroles.yaml new file mode 100644 index 00000000..d98a2da3 --- /dev/null +++ b/test_data/clusterroles.yaml @@ -0,0 +1,358 @@ +apiVersion: v1 +items: +- aggregationRule: + clusterRoleSelectors: + - matchLabels: + rbac.authorization.k8s.io/aggregate-to-admin: "true" + apiVersion: rbac.authorization.k8s.io/v1 + kind: ClusterRole + metadata: + annotations: + rbac.authorization.kubernetes.io/autoupdate: "true" + creationTimestamp: "2021-12-14T11:04:22Z" + labels: + kubernetes.io/bootstrapping: rbac-defaults + name: admin + resourceVersion: "98511715" + uid: 82a8bcf1-ca5f-4934-8775-45e623b10208 + rules: + - apiGroups: + - "" + resources: + - pods/attach + - pods/exec + - pods/portforward + - pods/proxy + - secrets + - services/proxy + verbs: + - get + - list + - watch + - apiGroups: + - "" + resources: + - serviceaccounts + verbs: + - impersonate + - apiGroups: + - "" + resources: + - pods + - pods/attach + - pods/exec + - pods/portforward + - pods/proxy + verbs: + - create + - delete + - deletecollection + - patch + - update + - apiGroups: + - "" + resources: + - configmaps + - endpoints + - persistentvolumeclaims + - replicationcontrollers + - replicationcontrollers/scale + - secrets + - serviceaccounts + - services + - services/proxy + verbs: + - create + - delete + - deletecollection + - patch + - update + - apiGroups: + - apps + resources: + - daemonsets + - deployments + - deployments/rollback + - deployments/scale + - replicasets + - replicasets/scale + - statefulsets + - statefulsets/scale + verbs: + - create + - delete + - deletecollection + - patch + - update + - apiGroups: + - autoscaling + resources: + - horizontalpodautoscalers + verbs: + - create + - delete + - deletecollection + - patch + - update + - apiGroups: + - batch + resources: + - cronjobs + - jobs + verbs: + - create + - delete + - deletecollection + - patch + - update + - apiGroups: + - extensions + resources: + - daemonsets + - deployments + - deployments/rollback + - deployments/scale + - ingresses + - networkpolicies + - replicasets + - replicasets/scale + - replicationcontrollers/scale + verbs: + - create + - delete + - deletecollection + - patch + - update + - apiGroups: + - policy + resources: + - poddisruptionbudgets + verbs: + - create + - delete + - deletecollection + - patch + - update + - apiGroups: + - networking.k8s.io + resources: + - ingresses + - networkpolicies + verbs: + - create + - delete + - deletecollection + - patch + - update + - apiGroups: + - "" + resources: + - configmaps + - endpoints + - persistentvolumeclaims + - persistentvolumeclaims/status + - pods + - replicationcontrollers + - replicationcontrollers/scale + - serviceaccounts + - services + - services/status + verbs: + - get + - list + - watch + - apiGroups: + - "" + resources: + - bindings + - events + - limitranges + - namespaces/status + - pods/log + - pods/status + - replicationcontrollers/status + - resourcequotas + - resourcequotas/status + verbs: + - get + - list + - watch + - apiGroups: + - "" + resources: + - namespaces + verbs: + - get + - list + - watch + - apiGroups: + - apps + resources: + - controllerrevisions + - daemonsets + - daemonsets/status + - deployments + - deployments/scale + - deployments/status + - replicasets + - replicasets/scale + - replicasets/status + - statefulsets + - statefulsets/scale + - statefulsets/status + verbs: + - get + - list + - watch + - apiGroups: + - autoscaling + resources: + - horizontalpodautoscalers + - horizontalpodautoscalers/status + verbs: + - get + - list + - watch + - apiGroups: + - batch + resources: + - cronjobs + - cronjobs/status + - jobs + - jobs/status + verbs: + - get + - list + - watch + - apiGroups: + - extensions + resources: + - daemonsets + - daemonsets/status + - deployments + - deployments/scale + - deployments/status + - ingresses + - ingresses/status + - networkpolicies + - replicasets + - replicasets/scale + - replicasets/status + - replicationcontrollers/scale + verbs: + - get + - list + - watch + - apiGroups: + - policy + resources: + - poddisruptionbudgets + - poddisruptionbudgets/status + verbs: + - get + - list + - watch + - apiGroups: + - networking.k8s.io + resources: + - ingresses + - ingresses/status + - networkpolicies + verbs: + - get + - list + - watch + - apiGroups: + - metrics.k8s.io + resources: + - pods + - nodes + verbs: + - get + - list + - watch + - apiGroups: + - kyverno.io + resources: + - policies + - clusterpolicies + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - wgpolicyk8s.io + resources: + - policyreport + - clusterpolicyreport + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - kyverno.io + resources: + - reportchangerequests + - clusterreportchangerequests + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - authorization.k8s.io + resources: + - localsubjectaccessreviews + verbs: + - create + - apiGroups: + - rbac.authorization.k8s.io + resources: + - rolebindings + - roles + verbs: + - create + - delete + - deletecollection + - get + - list + - patch + - update + - watch + - apiGroups: + - '*' + resources: + - policyreports + - policyreports/status + - clusterpolicyreports + - clusterpolicyreports/status + verbs: + - get + - list + - watch + - apiGroups: + - '*' + resources: + - policies + - policies/status + - clusterpolicies + - clusterpolicies/status + verbs: + - get + - list + - watch +kind: List +metadata: + resourceVersion: "" + selfLink: ""