From c48816ca5c226fb8e11a01434ff0ade8b91defd7 Mon Sep 17 00:00:00 2001 From: Violetta <38247153+vshakirova@users.noreply.github.com> Date: Thu, 14 Jan 2021 12:49:39 +0400 Subject: [PATCH 1/3] Add support for LimitRange (#1796) Signed-off-by: vshakirova --- integration/__tests__/app.tests.ts | 6 ++ src/common/rbac.ts | 3 +- src/extensions/renderer-api/k8s-api.ts | 2 + src/renderer/api/endpoints/index.ts | 1 + src/renderer/api/endpoints/limit-range.api.ts | 57 +++++++++++ .../components/+config-limit-ranges/index.ts | 3 + .../limit-range-details.scss | 12 +++ .../limit-range-details.tsx | 97 +++++++++++++++++++ .../limit-ranges.route.ts | 11 +++ .../+config-limit-ranges/limit-ranges.scss | 7 ++ .../limit-ranges.store.ts | 12 +++ .../+config-limit-ranges/limit-ranges.tsx | 53 ++++++++++ src/renderer/components/+config/config.tsx | 10 ++ .../+namespaces/namespace-details.tsx | 18 ++++ src/renderer/utils/rbac.ts | 1 + 15 files changed, 292 insertions(+), 1 deletion(-) create mode 100644 src/renderer/api/endpoints/limit-range.api.ts create mode 100644 src/renderer/components/+config-limit-ranges/index.ts create mode 100644 src/renderer/components/+config-limit-ranges/limit-range-details.scss create mode 100644 src/renderer/components/+config-limit-ranges/limit-range-details.tsx create mode 100644 src/renderer/components/+config-limit-ranges/limit-ranges.route.ts create mode 100644 src/renderer/components/+config-limit-ranges/limit-ranges.scss create mode 100644 src/renderer/components/+config-limit-ranges/limit-ranges.store.ts create mode 100644 src/renderer/components/+config-limit-ranges/limit-ranges.tsx diff --git a/integration/__tests__/app.tests.ts b/integration/__tests__/app.tests.ts index 410712e4f0..d68ab2f011 100644 --- a/integration/__tests__/app.tests.ts +++ b/integration/__tests__/app.tests.ts @@ -313,6 +313,12 @@ describe("Lens integration tests", () => { expectedSelector: "h5.title", expectedText: "Resource Quotas" }, + { + name: "Limit Ranges", + href: "limitranges", + expectedSelector: "h5.title", + expectedText: "Limit Ranges" + }, { name: "HPA", href: "hpa", diff --git a/src/common/rbac.ts b/src/common/rbac.ts index bd003e87a1..fbcf7c98d8 100644 --- a/src/common/rbac.ts +++ b/src/common/rbac.ts @@ -1,7 +1,7 @@ import { getHostedCluster } from "./cluster-store"; export type KubeResource = - "namespaces" | "nodes" | "events" | "resourcequotas" | "services" | + "namespaces" | "nodes" | "events" | "resourcequotas" | "services" | "limitranges" | "secrets" | "configmaps" | "ingresses" | "networkpolicies" | "persistentvolumeclaims" | "persistentvolumes" | "storageclasses" | "pods" | "daemonsets" | "deployments" | "statefulsets" | "replicasets" | "jobs" | "cronjobs" | "endpoints" | "customresourcedefinitions" | "horizontalpodautoscalers" | "podsecuritypolicies" | "poddisruptionbudgets"; @@ -23,6 +23,7 @@ export const apiResources: KubeApiResource[] = [ { resource: "horizontalpodautoscalers" }, { resource: "ingresses", group: "networking.k8s.io" }, { resource: "jobs", group: "batch" }, + { resource: "limitranges" }, { resource: "namespaces" }, { resource: "networkpolicies", group: "networking.k8s.io" }, { resource: "nodes" }, diff --git a/src/extensions/renderer-api/k8s-api.ts b/src/extensions/renderer-api/k8s-api.ts index fe04550fb7..071d8365ab 100644 --- a/src/extensions/renderer-api/k8s-api.ts +++ b/src/extensions/renderer-api/k8s-api.ts @@ -14,6 +14,7 @@ export { ConfigMap, configMapApi } from "../../renderer/api/endpoints"; export { Secret, secretsApi, ISecretRef } from "../../renderer/api/endpoints"; export { ReplicaSet, replicaSetApi } from "../../renderer/api/endpoints"; export { ResourceQuota, resourceQuotaApi } from "../../renderer/api/endpoints"; +export { LimitRange, limitRangeApi } from "../../renderer/api/endpoints"; export { HorizontalPodAutoscaler, hpaApi } from "../../renderer/api/endpoints"; export { PodDisruptionBudget, pdbApi } from "../../renderer/api/endpoints"; export { Service, serviceApi } from "../../renderer/api/endpoints"; @@ -46,6 +47,7 @@ export type { ConfigMapsStore } from "../../renderer/components/+config-maps/con export type { SecretsStore } from "../../renderer/components/+config-secrets/secrets.store"; export type { ReplicaSetStore } from "../../renderer/components/+workloads-replicasets/replicasets.store"; export type { ResourceQuotasStore } from "../../renderer/components/+config-resource-quotas/resource-quotas.store"; +export type { LimitRangesStore } from "../../renderer/components/+config-limit-ranges/limit-ranges.store"; export type { HPAStore } from "../../renderer/components/+config-autoscalers/hpa.store"; export type { PodDisruptionBudgetsStore } from "../../renderer/components/+config-pod-disruption-budgets/pod-disruption-budgets.store"; export type { ServiceStore } from "../../renderer/components/+network-services/services.store"; diff --git a/src/renderer/api/endpoints/index.ts b/src/renderer/api/endpoints/index.ts index f1202b9122..5ab54e7c3a 100644 --- a/src/renderer/api/endpoints/index.ts +++ b/src/renderer/api/endpoints/index.ts @@ -14,6 +14,7 @@ export * from "./events.api"; export * from "./hpa.api"; export * from "./ingress.api"; export * from "./job.api"; +export * from "./limit-range.api"; export * from "./namespaces.api"; export * from "./network-policy.api"; export * from "./nodes.api"; diff --git a/src/renderer/api/endpoints/limit-range.api.ts b/src/renderer/api/endpoints/limit-range.api.ts new file mode 100644 index 0000000000..bbb3941c87 --- /dev/null +++ b/src/renderer/api/endpoints/limit-range.api.ts @@ -0,0 +1,57 @@ +import { KubeObject } from "../kube-object"; +import { KubeApi } from "../kube-api"; +import { autobind } from "../../utils"; + +export enum LimitType { + CONTAINER = "Container", + POD = "Pod", + PVC = "PersistentVolumeClaim", +} + +export enum Resource { + MEMORY = "memory", + CPU = "cpu", + STORAGE = "storage", + EPHEMERAL_STORAGE = "ephemeral-storage", +} + +export enum LimitPart { + MAX = "max", + MIN = "min", + DEFAULT = "default", + DEFAULT_REQUEST = "defaultRequest", + MAX_LIMIT_REQUEST_RATIO = "maxLimitRequestRatio", +} + +type LimitRangeParts = Partial>>; + +export interface LimitRangeItem extends LimitRangeParts { + type: string +} + +@autobind() +export class LimitRange extends KubeObject { + static kind = "LimitRange"; + static namespaced = true; + static apiBase = "/api/v1/limitranges"; + + spec: { + limits: LimitRangeItem[]; + }; + + getContainerLimits() { + return this.spec.limits.filter(limit => limit.type === LimitType.CONTAINER); + } + + getPodLimits() { + return this.spec.limits.filter(limit => limit.type === LimitType.POD); + } + + getPVCLimits() { + return this.spec.limits.filter(limit => limit.type === LimitType.PVC); + } +} + +export const limitRangeApi = new KubeApi({ + objectConstructor: LimitRange, +}); diff --git a/src/renderer/components/+config-limit-ranges/index.ts b/src/renderer/components/+config-limit-ranges/index.ts new file mode 100644 index 0000000000..53308bdd18 --- /dev/null +++ b/src/renderer/components/+config-limit-ranges/index.ts @@ -0,0 +1,3 @@ +export * from "./limit-ranges"; +export * from "./limit-ranges.route"; +export * from "./limit-range-details"; diff --git a/src/renderer/components/+config-limit-ranges/limit-range-details.scss b/src/renderer/components/+config-limit-ranges/limit-range-details.scss new file mode 100644 index 0000000000..ff39ec514a --- /dev/null +++ b/src/renderer/components/+config-limit-ranges/limit-range-details.scss @@ -0,0 +1,12 @@ +.LimitRangeDetails { + + .DrawerItem { + > .name { + font-weight: $font-weight-normal; + padding-left: 4px; + } + .DrawerItem { + padding-top: 4px; + } + } +} diff --git a/src/renderer/components/+config-limit-ranges/limit-range-details.tsx b/src/renderer/components/+config-limit-ranges/limit-range-details.tsx new file mode 100644 index 0000000000..ab35505cc6 --- /dev/null +++ b/src/renderer/components/+config-limit-ranges/limit-range-details.tsx @@ -0,0 +1,97 @@ +import "./limit-range-details.scss"; + +import React from "react"; +import { observer } from "mobx-react"; +import { KubeObjectDetailsProps } from "../kube-object"; +import { LimitPart, LimitRange, LimitRangeItem, Resource } from "../../api/endpoints/limit-range.api"; +import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry"; +import { KubeObjectMeta } from "../kube-object/kube-object-meta"; +import { DrawerItem } from "../drawer/drawer-item"; +import { Badge } from "../badge"; + +interface Props extends KubeObjectDetailsProps { +} + +function renderLimit(limit: LimitRangeItem, part: LimitPart, resource: Resource) { + + const resourceLimit = limit[part]?.[resource]; + + if (!resourceLimit) { + return null; + } + + return ; +} + +function renderResourceLimits(limit: LimitRangeItem, resource: Resource) { + return ( + + {renderLimit(limit, LimitPart.MIN, resource)} + {renderLimit(limit, LimitPart.MAX, resource)} + {renderLimit(limit, LimitPart.DEFAULT, resource)} + {renderLimit(limit, LimitPart.DEFAULT_REQUEST, resource)} + {renderLimit(limit, LimitPart.MAX_LIMIT_REQUEST_RATIO, resource)} + + ); +} + +function renderLimitDetails(limits: LimitRangeItem[], resources: Resource[]) { + + return resources.map(resource => + + { + limits.map(limit => + renderResourceLimits(limit, resource) + ) + } + + ); +} + +@observer +export class LimitRangeDetails extends React.Component { + render() { + const { object: limitRange } = this.props; + + if (!limitRange) return null; + const containerLimits = limitRange.getContainerLimits(); + const podLimits = limitRange.getPodLimits(); + const pvcLimits = limitRange.getPVCLimits(); + + return ( +
+ + + {containerLimits.length > 0 && + + { + renderLimitDetails(containerLimits, [Resource.CPU, Resource.MEMORY, Resource.EPHEMERAL_STORAGE]) + } + + } + {podLimits.length > 0 && + + { + renderLimitDetails(podLimits, [Resource.CPU, Resource.MEMORY, Resource.EPHEMERAL_STORAGE]) + } + + } + {pvcLimits.length > 0 && + + { + renderLimitDetails(pvcLimits, [Resource.STORAGE]) + } + + } +
+ ); + } +} + +kubeObjectDetailRegistry.add({ + kind: "LimitRange", + apiVersions: ["v1"], + components: { + Details: (props) => + } +}); diff --git a/src/renderer/components/+config-limit-ranges/limit-ranges.route.ts b/src/renderer/components/+config-limit-ranges/limit-ranges.route.ts new file mode 100644 index 0000000000..09e3052350 --- /dev/null +++ b/src/renderer/components/+config-limit-ranges/limit-ranges.route.ts @@ -0,0 +1,11 @@ +import type { RouteProps } from "react-router"; +import { buildURL } from "../../../common/utils/buildUrl"; + +export const limitRangesRoute: RouteProps = { + path: "/limitranges" +}; + +export interface LimitRangeRouteParams { +} + +export const limitRangeURL = buildURL(limitRangesRoute.path); diff --git a/src/renderer/components/+config-limit-ranges/limit-ranges.scss b/src/renderer/components/+config-limit-ranges/limit-ranges.scss new file mode 100644 index 0000000000..e5de19acfb --- /dev/null +++ b/src/renderer/components/+config-limit-ranges/limit-ranges.scss @@ -0,0 +1,7 @@ +.LimitRanges { + .TableCell { + &.warning { + @include table-cell-warning; + } + } +} diff --git a/src/renderer/components/+config-limit-ranges/limit-ranges.store.ts b/src/renderer/components/+config-limit-ranges/limit-ranges.store.ts new file mode 100644 index 0000000000..bd760efadd --- /dev/null +++ b/src/renderer/components/+config-limit-ranges/limit-ranges.store.ts @@ -0,0 +1,12 @@ +import { autobind } from "../../../common/utils/autobind"; +import { KubeObjectStore } from "../../kube-object.store"; +import { apiManager } from "../../api/api-manager"; +import { LimitRange, limitRangeApi } from "../../api/endpoints/limit-range.api"; + +@autobind() +export class LimitRangesStore extends KubeObjectStore { + api = limitRangeApi; +} + +export const limitRangeStore = new LimitRangesStore(); +apiManager.registerStore(limitRangeStore); diff --git a/src/renderer/components/+config-limit-ranges/limit-ranges.tsx b/src/renderer/components/+config-limit-ranges/limit-ranges.tsx new file mode 100644 index 0000000000..8bb498c1c0 --- /dev/null +++ b/src/renderer/components/+config-limit-ranges/limit-ranges.tsx @@ -0,0 +1,53 @@ +import "./limit-ranges.scss"; + +import { RouteComponentProps } from "react-router"; +import { observer } from "mobx-react"; +import { KubeObjectListLayout } from "../kube-object/kube-object-list-layout"; +import { limitRangeStore } from "./limit-ranges.store"; +import { LimitRangeRouteParams } from "./limit-ranges.route"; +import React from "react"; +import { KubeObjectStatusIcon } from "../kube-object-status-icon"; +import { LimitRange } from "../../api/endpoints/limit-range.api"; + +enum sortBy { + name = "name", + namespace = "namespace", + age = "age", +} + +interface Props extends RouteComponentProps { +} + +@observer +export class LimitRanges extends React.Component { + render() { + return ( + item.getName(), + [sortBy.namespace]: (item: LimitRange) => item.getNs(), + [sortBy.age]: (item: LimitRange) => item.metadata.creationTimestamp, + }} + searchFilters={[ + (item: LimitRange) => item.getName(), + (item: LimitRange) => item.getNs(), + ]} + renderHeaderTitle={"Limit Ranges"} + renderTableHeader={[ + { title: "Name", className: "name", sortBy: sortBy.name }, + { className: "warning" }, + { title: "Namespace", className: "namespace", sortBy: sortBy.namespace }, + { title: "Age", className: "age", sortBy: sortBy.age }, + ]} + renderTableContents={(limitRange: LimitRange) => [ + limitRange.getName(), + , + limitRange.getNs(), + limitRange.getAge(), + ]} + /> + ); + } +} diff --git a/src/renderer/components/+config/config.tsx b/src/renderer/components/+config/config.tsx index bb70dd3fb5..e3158459ba 100644 --- a/src/renderer/components/+config/config.tsx +++ b/src/renderer/components/+config/config.tsx @@ -8,6 +8,7 @@ import { resourceQuotaRoute, ResourceQuotas, resourceQuotaURL } from "../+config import { pdbRoute, pdbURL, PodDisruptionBudgets } from "../+config-pod-disruption-budgets"; import { HorizontalPodAutoscalers, hpaRoute, hpaURL } from "../+config-autoscalers"; import { isAllowedResource } from "../../../common/rbac"; +import { LimitRanges, limitRangesRoute, limitRangeURL } from "../+config-limit-ranges"; @observer export class Config extends React.Component { @@ -42,6 +43,15 @@ export class Config extends React.Component { }); } + if (isAllowedResource("limitranges")) { + routes.push({ + title: "Limit Ranges", + component: LimitRanges, + url: limitRangeURL({ query }), + routePath: limitRangesRoute.path.toString(), + }); + } + if (isAllowedResource("horizontalpodautoscalers")) { routes.push({ title: "HPA", diff --git a/src/renderer/components/+namespaces/namespace-details.tsx b/src/renderer/components/+namespaces/namespace-details.tsx index f687630a96..5dfa93cea7 100644 --- a/src/renderer/components/+namespaces/namespace-details.tsx +++ b/src/renderer/components/+namespaces/namespace-details.tsx @@ -12,6 +12,7 @@ import { Spinner } from "../spinner"; import { resourceQuotaStore } from "../+config-resource-quotas/resource-quotas.store"; import { KubeObjectMeta } from "../kube-object/kube-object-meta"; import { kubeObjectDetailRegistry } from "../../api/kube-object-detail-registry"; +import { limitRangeStore } from "../+config-limit-ranges/limit-ranges.store"; interface Props extends KubeObjectDetailsProps { } @@ -24,8 +25,15 @@ export class NamespaceDetails extends React.Component { return resourceQuotaStore.getAllByNs(namespace); } + @computed get limitranges() { + const namespace = this.props.object.getName(); + + return limitRangeStore.getAllByNs(namespace); + } + componentDidMount() { resourceQuotaStore.loadAll(); + limitRangeStore.loadAll(); } render() { @@ -52,6 +60,16 @@ export class NamespaceDetails extends React.Component { ); })} + + {!this.limitranges && limitRangeStore.isLoading && } + {this.limitranges.map(limitrange => { + return ( + + {limitrange.getName()} + + ); + })} + ); } diff --git a/src/renderer/utils/rbac.ts b/src/renderer/utils/rbac.ts index 5f535c8109..36737ccf3a 100644 --- a/src/renderer/utils/rbac.ts +++ b/src/renderer/utils/rbac.ts @@ -25,4 +25,5 @@ export const ResourceNames: Record = { "horizontalpodautoscalers": "Horizontal Pod Autoscalers", "podsecuritypolicies": "Pod Security Policies", "poddisruptionbudgets": "Pod Disruption Budgets", + "limitranges": "Limit Ranges", }; From da3aa618d7764ec7e69ae83e7b95632292b8d1ff Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 14 Jan 2021 14:51:56 +0200 Subject: [PATCH 2/3] reset store on loading error Signed-off-by: Roman --- src/renderer/kube-object.store.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/renderer/kube-object.store.ts b/src/renderer/kube-object.store.ts index b1e452b1c5..d84517a26d 100644 --- a/src/renderer/kube-object.store.ts +++ b/src/renderer/kube-object.store.ts @@ -114,11 +114,16 @@ export abstract class KubeObjectStore extends ItemSt this.isLoaded = true; } catch (error) { console.error("Loading store items failed", { error, store: this }); + this.resetOnError(error); } finally { this.isLoading = false; } } + protected resetOnError(error: any) { + if (error) this.reset(); + } + protected async loadItem(params: { name: string; namespace?: string }): Promise { return this.api.get(params); } From cc8897e6e7c6f1bc6d1b8a7d7eb89b537af54a35 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 14 Jan 2021 15:18:40 +0200 Subject: [PATCH 3/3] added new cluster method: cluster.isAllowedResource Signed-off-by: Roman --- src/common/rbac.ts | 64 +++++++++++++------------------ src/main/cluster.ts | 37 +++++++++++++----- src/renderer/kube-object.store.ts | 12 +++++- 3 files changed, 64 insertions(+), 49 deletions(-) diff --git a/src/common/rbac.ts b/src/common/rbac.ts index afa5255712..de242b114a 100644 --- a/src/common/rbac.ts +++ b/src/common/rbac.ts @@ -7,50 +7,40 @@ export type KubeResource = "endpoints" | "customresourcedefinitions" | "horizontalpodautoscalers" | "podsecuritypolicies" | "poddisruptionbudgets"; export interface KubeApiResource { - kind: string; // resource type - resource: KubeResource; // valid resource name + kind: string; // resource type (e.g. "Namespace") + apiName: KubeResource; // valid api resource name (e.g. "namespaces") group?: string; // api-group } // TODO: auto-populate all resources dynamically (see: kubectl api-resources -o=wide -v=7) export const apiResources: KubeApiResource[] = [ - { kind: "ConfigMap", resource: "configmaps" }, - { kind: "CronJob", resource: "cronjobs", group: "batch" }, - { kind: "CustomResourceDefinition", resource: "customresourcedefinitions", group: "apiextensions.k8s.io" }, - { kind: "DaemonSet", resource: "daemonsets", group: "apps" }, - { kind: "Deployment", resource: "deployments", group: "apps" }, - { kind: "Endpoint", resource: "endpoints" }, - { kind: "Event", resource: "events" }, - { kind: "HorizontalPodAutoscaler", resource: "horizontalpodautoscalers" }, - { kind: "Ingress", resource: "ingresses", group: "networking.k8s.io" }, - { kind: "Job", resource: "jobs", group: "batch" }, - { kind: "Namespace", resource: "namespaces" }, - { kind: "LimitRange", resource: "limitranges" }, - { kind: "NetworkPolicy", resource: "networkpolicies", group: "networking.k8s.io" }, - { kind: "Node", resource: "nodes" }, - { kind: "PersistentVolume", resource: "persistentvolumes" }, - { kind: "PersistentVolumeClaim", resource: "persistentvolumeclaims" }, - { kind: "Pod", resource: "pods" }, - { kind: "PodDisruptionBudget", resource: "poddisruptionbudgets" }, - { kind: "PodSecurityPolicy", resource: "podsecuritypolicies" }, - { kind: "ResourceQuota", resource: "resourcequotas" }, - { kind: "ReplicaSet", resource: "replicasets", group: "apps" }, - { kind: "Secret", resource: "secrets" }, - { kind: "Service", resource: "services" }, - { kind: "StatefulSet", resource: "statefulsets", group: "apps" }, - { kind: "StorageClass", resource: "storageclasses", group: "storage.k8s.io" }, + { kind: "ConfigMap", apiName: "configmaps" }, + { kind: "CronJob", apiName: "cronjobs", group: "batch" }, + { kind: "CustomResourceDefinition", apiName: "customresourcedefinitions", group: "apiextensions.k8s.io" }, + { kind: "DaemonSet", apiName: "daemonsets", group: "apps" }, + { kind: "Deployment", apiName: "deployments", group: "apps" }, + { kind: "Endpoint", apiName: "endpoints" }, + { kind: "Event", apiName: "events" }, + { kind: "HorizontalPodAutoscaler", apiName: "horizontalpodautoscalers" }, + { kind: "Ingress", apiName: "ingresses", group: "networking.k8s.io" }, + { kind: "Job", apiName: "jobs", group: "batch" }, + { kind: "Namespace", apiName: "namespaces" }, + { kind: "LimitRange", apiName: "limitranges" }, + { kind: "NetworkPolicy", apiName: "networkpolicies", group: "networking.k8s.io" }, + { kind: "Node", apiName: "nodes" }, + { kind: "PersistentVolume", apiName: "persistentvolumes" }, + { kind: "PersistentVolumeClaim", apiName: "persistentvolumeclaims" }, + { kind: "Pod", apiName: "pods" }, + { kind: "PodDisruptionBudget", apiName: "poddisruptionbudgets" }, + { kind: "PodSecurityPolicy", apiName: "podsecuritypolicies" }, + { kind: "ResourceQuota", apiName: "resourcequotas" }, + { kind: "ReplicaSet", apiName: "replicasets", group: "apps" }, + { kind: "Secret", apiName: "secrets" }, + { kind: "Service", apiName: "services" }, + { kind: "StatefulSet", apiName: "statefulsets", group: "apps" }, + { kind: "StorageClass", apiName: "storageclasses", group: "storage.k8s.io" }, ]; -export function isAllowedResourceType(kind: string): boolean { - const apiResource = apiResources.find(resource => resource.kind === kind); - - if (apiResource) { - return getHostedCluster().allowedResources.includes(apiResource.resource); - } - - return true; // allowed by default for other resources -} - export function isAllowedResource(resources: KubeResource | KubeResource[]) { if (!Array.isArray(resources)) { resources = [resources]; diff --git a/src/main/cluster.ts b/src/main/cluster.ts index 03e97d47dd..65e76296c3 100644 --- a/src/main/cluster.ts +++ b/src/main/cluster.ts @@ -216,7 +216,7 @@ export class Cluster implements ClusterModel, ClusterState { * @computed */ @computed get name() { - return this.preferences.clusterName || this.contextName; + return this.preferences.clusterName || this.contextName; } /** @@ -271,7 +271,8 @@ export class Cluster implements ClusterModel, ClusterState { * @param port port where internal auth proxy is listening * @internal */ - @action async init(port: number) { + @action + async init(port: number) { try { this.contextHandler = new ContextHandler(this); this.kubeconfigManager = await KubeconfigManager.create(this, this.contextHandler, port); @@ -323,7 +324,8 @@ export class Cluster implements ClusterModel, ClusterState { * @param force force activation * @internal */ - @action async activate(force = false) { + @action + async activate(force = false) { if (this.activated && !force) { return this.pushState(); } @@ -362,7 +364,8 @@ export class Cluster implements ClusterModel, ClusterState { /** * @internal */ - @action async reconnect() { + @action + async reconnect() { logger.info(`[CLUSTER]: reconnect`, this.getMeta()); this.contextHandler?.stopServer(); await this.contextHandler?.ensureServer(); @@ -389,7 +392,8 @@ export class Cluster implements ClusterModel, ClusterState { * @internal * @param opts refresh options */ - @action async refresh(opts: ClusterRefreshOptions = {}) { + @action + async refresh(opts: ClusterRefreshOptions = {}) { logger.info(`[CLUSTER]: refresh`, this.getMeta()); await this.whenInitialized; await this.refreshConnectionStatus(); @@ -409,7 +413,8 @@ export class Cluster implements ClusterModel, ClusterState { /** * @internal */ - @action async refreshMetadata() { + @action + async refreshMetadata() { logger.info(`[CLUSTER]: refreshMetadata`, this.getMeta()); const metadata = await detectorRegistry.detectForCluster(this); const existingMetadata = this.metadata; @@ -420,7 +425,8 @@ export class Cluster implements ClusterModel, ClusterState { /** * @internal */ - @action async refreshConnectionStatus() { + @action + async refreshConnectionStatus() { const connectionStatus = await this.getConnectionStatus(); this.online = connectionStatus > ClusterStatus.Offline; @@ -430,7 +436,8 @@ export class Cluster implements ClusterModel, ClusterState { /** * @internal */ - @action async refreshAllowedResources() { + @action + async refreshAllowedResources() { this.allowedNamespaces = await this.getAllowedNamespaces(); this.allowedResources = await this.getAllowedResources(); } @@ -657,7 +664,7 @@ export class Cluster implements ClusterModel, ClusterState { for (const namespace of this.allowedNamespaces.slice(0, 10)) { if (!this.resourceAccessStatuses.get(apiResource)) { const result = await this.canI({ - resource: apiResource.resource, + resource: apiResource.apiName, group: apiResource.group, verb: "list", namespace @@ -672,9 +679,19 @@ export class Cluster implements ClusterModel, ClusterState { return apiResources .filter((resource) => this.resourceAccessStatuses.get(resource)) - .map(apiResource => apiResource.resource); + .map(apiResource => apiResource.apiName); } catch (error) { return []; } } + + isAllowedResource(kind: string): boolean { + const apiResource = apiResources.find(resource => resource.kind === kind || resource.apiName === kind); + + if (apiResource) { + return this.allowedResources.includes(apiResource.apiName); + } + + return true; // allowed by default for other resources + } } diff --git a/src/renderer/kube-object.store.ts b/src/renderer/kube-object.store.ts index d84517a26d..956f5aa5f6 100644 --- a/src/renderer/kube-object.store.ts +++ b/src/renderer/kube-object.store.ts @@ -1,3 +1,4 @@ +import type { Cluster } from "../main/cluster"; import { action, observable, reaction } from "mobx"; import { autobind } from "./utils"; import { KubeObject } from "./api/kube-object"; @@ -6,7 +7,6 @@ import { ItemStore } from "./item.store"; import { apiManager } from "./api/api-manager"; import { IKubeApiQueryParams, KubeApi } from "./api/kube-api"; import { KubeJsonApiData } from "./api/kube-json-api"; -import { isAllowedResourceType } from "../common/rbac"; export interface KubeObjectStoreLoadingParams { namespaces: string[]; @@ -76,8 +76,16 @@ export abstract class KubeObjectStore extends ItemSt } } + protected async resolveCluster(): Promise { + const { getHostedCluster } = await import("../common/cluster-store"); + + return getHostedCluster(); + } + protected async loadItems({ namespaces, api }: KubeObjectStoreLoadingParams): Promise { - if (isAllowedResourceType(api.kind)) { + const cluster = await this.resolveCluster(); + + if (cluster.isAllowedResource(api.kind)) { if (api.isNamespaced) { return Promise .all(namespaces.map(namespace => api.list({ namespace })))