From 1bf24db7972d3d2cf6df3d20dcaf87f0a8e41e64 Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Thu, 27 Apr 2023 10:32:41 -0400 Subject: [PATCH] chore: Extract @k8slens/kube-object package Signed-off-by: Sebastian Malton --- .../navigate-to.injectable.ts} | 2 +- .../route.injectable.ts} | 4 +- .../k8s-api/__tests__/api-manager.test.ts | 6 +- .../src/common/k8s-api/__tests__/crd.test.ts | 4 +- .../k8s-api/__tests__/deployment.api.test.ts | 2 +- .../k8s-api/__tests__/endpoint.api.test.ts | 2 +- .../k8s-api/__tests__/ingress.api.test.ts | 2 +- .../k8s-api/__tests__/kube-api-parse.test.ts | 1 - .../kube-api-version-detection.test.ts | 3 +- .../common/k8s-api/__tests__/kube-api.test.ts | 65 +- .../__tests__/kube-object.store.test.ts | 4 +- .../k8s-api/__tests__/kube-object.test.ts | 2 +- .../common/k8s-api/__tests__/pods.api.test.ts | 2 +- .../common/k8s-api/api-manager/api-manager.ts | 2 +- ...create-custom-resource-store.injectable.ts | 2 +- .../k8s-api/api-manager/resource.store.ts | 2 +- .../create-kube-api-for-cluster.injectable.ts | 2 +- ...-kube-api-for-remote-cluster.injectable.ts | 2 +- .../endpoints/cluster-role-binding.api.ts | 43 +- .../k8s-api/endpoints/cluster-role.api.ts | 35 +- .../common/k8s-api/endpoints/cluster.api.ts | 57 +- .../k8s-api/endpoints/component-status.api.ts | 22 +- .../k8s-api/endpoints/config-map.api.ts | 39 +- .../common/k8s-api/endpoints/cron-job.api.ts | 101 +-- .../custom-resource-definition.api.ts | 228 +---- .../endpoints/daemon-set.api.injectable.ts | 2 +- .../k8s-api/endpoints/daemon-set.api.ts | 99 +- .../k8s-api/endpoints/deployment.api.ts | 118 +-- .../common/k8s-api/endpoints/endpoint.api.ts | 107 +-- .../common/k8s-api/endpoints/events.api.ts | 127 +-- .../request-details.injectable.ts | 6 +- .../horizontal-pod-autoscaler.api.ts | 358 +------- .../src/common/k8s-api/endpoints/index.ts | 1 - .../k8s-api/endpoints/ingress-class.api.ts | 85 +- .../common/k8s-api/endpoints/ingress.api.ts | 195 +--- .../src/common/k8s-api/endpoints/job.api.ts | 89 +- .../src/common/k8s-api/endpoints/lease.api.ts | 41 +- .../k8s-api/endpoints/limit-range.api.ts | 55 +- .../metrics.api/request-metrics.injectable.ts | 1 - ...sistent-volume-claim-metrics.injectable.ts | 2 +- ...-pod-metrics-for-daemon-sets.injectable.ts | 2 +- ...-pod-metrics-for-deployments.injectable.ts | 2 +- ...request-pod-metrics-for-jobs.injectable.ts | 2 +- ...pod-metrics-for-replica-sets.injectable.ts | 2 +- ...od-metrics-for-stateful-sets.injectable.ts | 2 +- .../request-pod-metrics.injectable.ts | 2 +- .../mutating-webhook-configuration.api.ts | 144 +-- .../common/k8s-api/endpoints/namespace.api.ts | 42 +- .../k8s-api/endpoints/network-policy.api.ts | 118 +-- .../src/common/k8s-api/endpoints/node.api.ts | 248 +----- .../endpoints/persistent-volume-claim.api.ts | 57 +- .../endpoints/persistent-volume.api.ts | 97 +- .../endpoints/pod-disruption-budget.api.ts | 71 +- .../k8s-api/endpoints/pod-metrics.api.ts | 47 +- .../endpoints/pod-security-policy.api.ts | 105 +-- .../src/common/k8s-api/endpoints/pod.api.ts | 842 +----------------- .../k8s-api/endpoints/priority-class.api.ts | 51 +- .../k8s-api/endpoints/replica-set.api.ts | 81 +- .../endpoints/replication-controller.api.ts | 120 +-- .../request-patch.injectable.ts | 8 +- .../request-update.injectable.ts | 2 +- .../k8s-api/endpoints/resource-quota.api.ts | 59 +- .../k8s-api/endpoints/role-binding.api.ts | 38 +- .../src/common/k8s-api/endpoints/role.api.ts | 30 +- .../k8s-api/endpoints/runtime-class.api.ts | 58 +- .../common/k8s-api/endpoints/secret.api.ts | 67 +- .../self-subject-rules-reviews.api.ts | 57 +- .../k8s-api/endpoints/service-account.api.ts | 45 +- .../common/k8s-api/endpoints/service.api.ts | 123 +-- .../k8s-api/endpoints/stateful-set.api.ts | 120 +-- .../k8s-api/endpoints/storage-class.api.ts | 80 +- .../endpoints/types/security-context.ts | 55 -- .../validating-webhook-configuration.api.ts | 33 +- .../endpoints/vertical-pod-autoscaler.api.ts | 133 +-- packages/core/src/common/k8s-api/json-api.ts | 10 +- packages/core/src/common/k8s-api/kube-api.ts | 62 +- .../core/src/common/k8s-api/kube-json-api.ts | 29 +- .../src/common/k8s-api/kube-object.store.ts | 14 +- .../core/src/common/k8s-api/kube-object.ts | 743 ---------------- .../src/common/k8s-api/kube-watch-event.ts | 2 +- .../core/src/extensions/common-api/k8s-api.ts | 21 +- ...tems-when-cluster-is-not-relevant.test.tsx | 19 +- ...vely-hide-kube-object-detail-item.test.tsx | 4 +- ...tems-when-cluster-is-not-relevant.test.tsx | 2 +- ...tively-hide-kube-object-menu-item.test.tsx | 2 +- ...uses-when-cluster-is-not-relevant.test.tsx | 2 +- ...eactively-hide-kube-object-status.test.tsx | 2 +- .../show-status-for-a-kube-object.test.tsx | 2 +- .../edit-namespace-from-new-tab.test.tsx | 9 +- ...espace-from-previously-opened-tab.test.tsx | 2 +- .../features/cluster/workloads/pods.test.tsx | 18 +- .../features/pod-logs/download-logs.test.tsx | 38 +- .../call-for-helm-manifest.injectable.ts | 2 +- ...r-kube-resources-by-manifest.injectable.ts | 2 +- .../get-helm-release-resources.injectable.ts | 2 +- .../get-helm-release-resources.test.ts | 2 +- .../node-shell-session/node-shell-session.ts | 10 +- ...setup-auto-crd-api-creations.injectable.ts | 4 +- .../__tests__/cronjob.store.test.ts | 2 +- .../__tests__/daemonset.store.test.ts | 2 +- .../__tests__/deployments.store.test.ts | 4 +- .../components/__tests__/job.store.test.ts | 2 +- .../components/__tests__/nodes.api.test.ts | 2 +- .../components/__tests__/pods.store.test.ts | 2 +- .../__tests__/replicaset.store.test.ts | 2 +- .../__tests__/statefulset.store.test.ts | 2 +- .../columns/default-category.injectable.tsx | 2 +- .../catalog/get-label-badges.injectable.tsx | 2 +- .../components/cluster/cluster-issues.tsx | 9 +- .../cluster-overview-store.ts | 14 +- .../registered-commands.injectable.ts | 4 +- .../details.test.tsx | 18 +- .../details.tsx | 18 +- .../get-metric-name.ts | 13 +- .../get-metrics.injectable.ts | 13 +- .../list-view.tsx | 2 +- .../metric-parser-v1.ts | 10 +- .../metric-parser-v2.ts | 14 +- .../metric-parser.test.ts | 94 +- .../store.ts | 3 +- .../config-leases/lease-details.tsx | 2 +- .../components/config-leases/leases.tsx | 2 +- .../components/config-leases/store.ts | 3 +- .../limit-range-details.tsx | 4 +- .../components/config-limit-ranges/store.ts | 3 +- .../config-maps/config-map-details.tsx | 44 +- .../renderer/components/config-maps/store.ts | 3 +- .../details.test.tsx | 9 +- .../mutating-webhook-configuration-store.ts | 16 +- ...utating-webhook-configurations-details.tsx | 2 +- .../webhook-config.tsx | 8 +- .../pod-distruption-budgets.test.tsx | 4 +- .../pod-disruption-budgets-details.tsx | 2 +- .../pod-disruption-budgets.tsx | 2 +- .../config-pod-disruption-budgets/store.ts | 3 +- .../priority-classes-details.tsx | 2 +- .../priority-classes.tsx | 2 +- .../config-priority-classes/store.ts | 3 +- .../add-dialog/view.tsx | 24 +- .../resource-quota-details.tsx | 2 +- .../config-resource-quotas/store.ts | 3 +- .../runtime-classes-details-tolerations.tsx | 2 +- .../runtime-classes-details.tsx | 2 +- .../runtime-classes-tolerations.tsx | 2 +- .../runtime-classes.tsx | 2 +- .../config-runtime-classes/store.ts | 3 +- .../__tests__/secret-details.test.tsx | 2 +- .../config-secrets/add-dialog/view.tsx | 8 +- .../config-secrets/secret-details.tsx | 24 +- .../components/config-secrets/store.ts | 3 +- .../details.test.tsx | 9 +- .../validating-webhook-configuration-store.ts | 16 +- ...idating-webhook-configurations-details.tsx | 2 +- .../config-vertical-pod-autoscalers/store.ts | 3 +- .../vpa-details.tsx | 76 +- .../confirm-dialog/confirm-dialog.tsx | 4 +- .../custom-resource-details.test.tsx | 4 +- .../custom-resources/crd-details.tsx | 2 +- .../custom-resources/crd-resource-details.tsx | 8 +- .../custom-resources/definition.store.ts | 4 +- .../components/dock/create-resource/view.tsx | 2 +- .../edit-resource-model.injectable.tsx | 2 +- .../request-kube-resource.injectable.ts | 5 +- .../edit-resource-tab.injectable.ts | 2 +- .../components/dock/edit-resource/store.ts | 2 +- .../components/dock/logs/__test__/pod.mock.ts | 2 +- .../dock/logs/call-for-logs.injectable.ts | 2 +- .../logs/create-pod-logs-tab.injectable.ts | 2 +- .../create-workload-logs-tab.injectable.ts | 2 +- .../dock/logs/download-all-logs.injectable.ts | 4 +- .../dock/logs/load-logs.injectable.ts | 2 +- .../components/dock/logs/logs-view-model.ts | 2 +- .../dock/logs/reload-logs.injectable.ts | 2 +- .../dock/logs/resource-selector.tsx | 2 +- .../renderer/components/dock/logs/store.ts | 2 +- .../components/drawer/drawer-item-labels.tsx | 2 +- .../components/events/event-details.tsx | 2 +- .../src/renderer/components/events/events.tsx | 11 +- .../components/events/kube-event-details.tsx | 2 +- .../components/events/kube-event-icon.tsx | 3 +- .../src/renderer/components/events/store.ts | 8 +- .../helm-releases/dialog/dialog.tsx | 2 +- .../release-details-model.injectable.tsx | 4 +- ...rrent-kube-object-in-details.injectable.ts | 2 +- ...isruption-budget-detail-item.injectable.ts | 2 +- ...ation-controller-detail-item.injectable.ts | 6 +- ...-object-matches-to-kind-and-api-version.ts | 2 +- .../kube-object-detail-registration.ts | 2 +- .../kube-object-details.tsx | 2 +- .../kube-object-list-layout.tsx | 4 +- .../kube-object-menu-item-injection-token.ts | 2 +- .../kube-object-menu-items.injectable.ts | 2 +- .../kube-object-menu-registration.ts | 2 +- .../kube-object-menu.test.tsx | 2 +- .../kube-object-menu/kube-object-menu.tsx | 2 +- .../on-context-menu-open.injectable.ts | 2 +- .../kube-object-meta/kube-object-meta.tsx | 37 +- .../kube-object-status-icon.tsx | 2 +- .../kube-object-status-registration.ts | 2 +- ...kube-object-status-text-injection-token.ts | 2 +- ...ject-status-texts-for-object.injectable.ts | 2 +- ...e-account-kube-config-dialog.injectable.ts | 2 +- .../namespaces/add-dialog/dialog.tsx | 4 +- .../namespaces/metrics-details-component.tsx | 2 +- .../namespaces/metrics.injectable.ts | 2 +- .../namespaces/namespace-details.tsx | 2 +- ...ext-menu-overriding-listener.injectable.ts | 2 +- .../namespace-select-filter.test.tsx | 2 +- .../namespaces/namespace-store.test.ts | 2 +- .../namespaces/namespace-tree-view.test.tsx | 4 +- .../namespaces/namespace-tree-view.tsx | 2 +- .../request-delete-namespace.injectable.ts | 2 +- ...uest-delete-normal-namespace.injectable.ts | 2 +- ...request-delete-sub-namespace.injectable.ts | 2 +- .../renderer/components/namespaces/store.ts | 4 +- .../network-endpoints/endpoint-details.tsx | 2 +- .../endpoint-subset-list.tsx | 2 +- .../components/network-endpoints/store.ts | 3 +- .../ingress-class-details.tsx | 2 +- .../ingress-class-menu.injectable.tsx | 21 +- .../ingress-class-set-default.injectable.ts | 7 +- .../network-ingresses/ingress-class-store.ts | 3 +- .../network-ingresses/ingress-classes.tsx | 2 +- .../network-ingresses/ingress-details.tsx | 5 +- .../network-ingresses/ingress-store.ts | 3 +- .../network-ingresses/ingresses.tsx | 2 +- .../metrics-details-component.tsx | 2 +- .../network-ingresses/metrics.injectable.ts | 2 +- .../__tests__/network-policy-details.test.tsx | 2 +- .../network-policy-details.tsx | 9 +- .../components/network-policies/store.ts | 3 +- .../service-details-endpoint.tsx | 2 +- .../network-services/service-details.tsx | 2 +- .../service-port-component.tsx | 2 +- .../components/network-services/services.tsx | 2 +- .../components/network-services/store.ts | 3 +- .../components/nodes/details-resources.tsx | 2 +- .../src/renderer/components/nodes/details.tsx | 2 +- .../nodes/metrics-details-component.tsx | 2 +- .../components/nodes/metrics.injectable.ts | 2 +- .../src/renderer/components/nodes/route.tsx | 12 +- .../src/renderer/components/nodes/store.ts | 4 +- .../pod-security-policy-details.tsx | 2 +- .../components/pod-security-policies/store.ts | 3 +- .../resource-metrics/resource-metrics.tsx | 4 +- .../storage-classes/storage-class-details.tsx | 2 +- .../storage-classes/store.injectable.ts | 2 +- .../components/storage-classes/store.ts | 5 +- .../metrics-details-component.tsx | 2 +- .../metrics.injectable.ts | 2 +- .../components/storage-volume-claims/store.ts | 3 +- .../volume-claim-details.tsx | 2 +- ...nt-volumes-by-storage-class.injectable.ts} | 2 +- .../components/storage-volumes/store.ts | 7 +- .../storage-volumes/volume-details-list.tsx | 2 +- .../storage-volumes/volume-details.tsx | 2 +- .../test-utils/get-application-builder.tsx | 2 +- .../__tests__/dialog.test.tsx | 2 +- .../cluster-role-bindings/details.tsx | 2 +- .../dialog/open.injectable.ts | 2 +- .../dialog/state.injectable.ts | 2 +- .../cluster-role-bindings/dialog/view.tsx | 12 +- .../cluster-role-bindings/store.ts | 4 +- .../cluster-roles/add-dialog/view.tsx | 2 +- .../user-management/cluster-roles/details.tsx | 2 +- .../user-management/cluster-roles/store.ts | 3 +- .../components/user-management/hashers.ts | 2 +- .../role-bindings/__tests__/dialog.test.tsx | 2 +- .../user-management/role-bindings/details.tsx | 2 +- .../role-bindings/dialog/open.injectable.ts | 2 +- .../role-bindings/dialog/state.injectable.ts | 2 +- .../role-bindings/dialog/view.tsx | 11 +- .../user-management/role-bindings/store.ts | 4 +- .../user-management/roles/add-dialog/view.tsx | 2 +- .../user-management/roles/details.tsx | 2 +- .../components/user-management/roles/store.ts | 3 +- .../service-accounts/create-dialog/view.tsx | 2 +- .../service-accounts/details.tsx | 2 +- .../service-accounts/secret.tsx | 2 +- .../service-accounts/service-account-menu.tsx | 2 +- .../user-management/service-accounts/store.ts | 3 +- .../workloads-cronjobs/cron-job-menu.tsx | 7 +- .../workloads-cronjobs/cronjob-details.tsx | 4 +- .../components/workloads-cronjobs/store.ts | 3 +- .../trigger-dialog/open.injectable.ts | 2 +- .../trigger-dialog/state.injectable.ts | 2 +- .../trigger-dialog/view.tsx | 5 +- .../daemonset-details.tsx | 2 +- .../workloads-daemonsets/daemonset-menu.tsx | 15 +- .../workloads-daemonsets/daemonsets.tsx | 2 +- .../metrics-details-component.tsx | 2 +- .../metrics.injectable.ts | 2 +- .../components/workloads-daemonsets/store.ts | 5 +- .../deployment-details.tsx | 2 +- .../workloads-deployments/deployment-menu.tsx | 5 +- .../deployment-replicasets.tsx | 2 +- .../workloads-deployments/deployments.tsx | 2 +- .../metrics-details-component.tsx | 2 +- .../metrics.injectable.ts | 2 +- .../scale/dialog-state.injectable.ts | 2 +- .../scale/dialog.test.tsx | 8 +- .../workloads-deployments/scale/dialog.tsx | 5 +- .../scale/open.injectable.ts | 2 +- .../components/workloads-deployments/store.ts | 5 +- .../get-jobs-by-owner.injectable.ts | 2 +- .../components/workloads-jobs/job-details.tsx | 2 +- .../metrics-details-component.tsx | 2 +- .../workloads-jobs/metrics.injectable.ts | 2 +- .../components/workloads-jobs/store.ts | 6 +- .../__tests__/pod-container-env.test.tsx | 4 +- .../__tests__/pod-tolerations.test.tsx | 2 +- .../container-metrics.injectable.ts | 2 +- .../containers/pod-details-containers.tsx | 2 +- .../pod-details-init-containers.tsx | 2 +- .../details/volumes/variant-helpers.tsx | 3 +- .../details/volumes/variant.tsx | 2 +- .../variants/__tests__/ceph-fs.test.tsx | 4 +- .../volumes/variants/projected.test.tsx | 4 +- .../workloads-pods/details/volumes/view.tsx | 2 +- .../get-pod-by-id.injectable.ts | 2 +- .../get-pods-by-owner-id.injectable.ts | 2 +- .../workloads-pods/metrics.injectable.ts | 2 +- .../workloads-pods/pod-container-env.tsx | 2 +- .../workloads-pods/pod-container-port.tsx | 2 +- .../workloads-pods/pod-details-affinities.tsx | 2 +- .../pod-details-container-metrics.tsx | 2 +- .../workloads-pods/pod-details-container.tsx | 2 +- .../workloads-pods/pod-details-list.tsx | 3 +- .../workloads-pods/pod-details-secrets.tsx | 31 +- .../workloads-pods/pod-details-statuses.tsx | 2 +- .../pod-details-tolerations.tsx | 2 +- .../components/workloads-pods/pod-details.tsx | 4 +- .../pod-metrics-details-component.tsx | 2 +- .../workloads-pods/pod-tolerations.tsx | 2 +- .../workloads-pods/secret-key.test.tsx | 2 +- .../components/workloads-pods/secret-key.tsx | 2 +- .../components/workloads-pods/store.ts | 4 +- .../metrics-details-component.tsx | 2 +- .../metrics.injectable.ts | 2 +- .../replica-set-menu.tsx | 2 +- .../replicaset-details.tsx | 2 +- .../scale-dialog/dialog.test.tsx | 9 +- .../scale-dialog/dialog.tsx | 5 +- .../scale-dialog/open.injectable.ts | 2 +- .../scale-dialog/state.injectable.ts | 2 +- .../components/workloads-replicasets/store.ts | 5 +- .../index.ts | 4 +- ...eplication-controller-details.module.scss} | 0 .../replication-controller-details.tsx} | 11 +- ...n-controller-sidebar-items.injectable.tsx} | 8 +- ...eplication-controller-store.injectable.ts} | 5 +- .../replication-controller-store.ts | 11 + ...-controllers-route-component.injectable.ts | 11 +- .../replication-controllers.module.scss} | 0 .../replication-controllers.tsx | 83 ++ .../replicationcontroller-store.ts | 23 - .../replicationcontrollers.tsx | 86 -- .../metrics-details-component.tsx | 2 +- .../metrics.injectable.ts | 2 +- .../scale/dialog-state.injectable.ts | 2 +- .../scale/dialog.test.tsx | 8 +- .../workloads-statefulsets/scale/dialog.tsx | 5 +- .../scale/open-dialog.injectable.ts | 2 +- .../statefulset-details.tsx | 2 +- .../statefulset-menu.tsx | 15 +- .../workloads-statefulsets/statefulsets.tsx | 2 +- .../workloads-statefulsets/store.ts | 5 +- .../__snapshots__/cluster-frame.test.tsx.snap | 8 +- ...rame-layout-child-component.injectable.tsx | 2 +- .../cluster-frame/cluster-frame.test.tsx | 2 +- .../core/src/renderer/kube-object/handler.ts | 2 +- .../handlers/stateful-set.injectable.ts | 2 +- packages/kube-object/.eslintrc.js | 6 + packages/kube-object/.prettierrc | 1 + packages/kube-object/index.ts | 11 + packages/kube-object/jest.config.js | 1 + packages/kube-object/package.json | 49 + packages/kube-object/src/api-types.ts | 401 +++++++++ packages/kube-object/src/kube-object.ts | 266 ++++++ packages/kube-object/src/kube-status.ts | 53 ++ .../src/specifics/cluster-role-binding.ts | 43 + .../kube-object/src/specifics/cluster-role.ts | 36 + packages/kube-object/src/specifics/cluster.ts | 71 ++ .../src/specifics/component-status.ts | 28 + .../kube-object/src/specifics/config-map.ts | 41 + .../kube-object/src/specifics/cron-job.ts | 69 ++ .../specifics/custom-resource-definition.ts | 220 +++++ .../kube-object/src/specifics/daemon-set.ts | 77 ++ .../kube-object/src/specifics/deployment.ts | 89 ++ .../kube-object/src/specifics/endpoint.ts | 102 +++ packages/kube-object/src/specifics/events.ts | 143 +++ .../specifics/horizontal-pod-autoscaler.ts | 340 +++++++ packages/kube-object/src/specifics/index.ts | 47 + .../src/specifics/ingress-class.ts | 61 ++ packages/kube-object/src/specifics/ingress.ts | 203 +++++ packages/kube-object/src/specifics/job.ts | 91 ++ packages/kube-object/src/specifics/lease.ts | 43 + .../kube-object/src/specifics/limit-range.ts | 57 ++ .../mutating-webhook-configuration.ts | 193 ++++ .../kube-object/src/specifics/namespace.ts | 44 + .../src/specifics/network-policy.ts | 117 +++ .../src/specifics}/node.test.ts | 7 +- packages/kube-object/src/specifics/node.ts | 253 ++++++ .../src/specifics/persistent-volume-claim.ts | 62 ++ .../src/specifics/persistent-volume.ts | 101 +++ .../src/specifics/pod-disruption-budget.ts | 73 ++ .../kube-object/src/specifics/pod-metrics.ts | 44 + .../src/specifics/pod-security-policy.ts | 107 +++ .../src/specifics/pod.test.ts} | 108 ++- packages/kube-object/src/specifics/pod.ts | 834 +++++++++++++++++ .../src/specifics/priority-class.ts | 55 ++ .../kube-object/src/specifics/replica-set.ts | 73 ++ .../src/specifics/replication-controller.ts | 122 +++ .../src/specifics/resource-quota.ts | 61 ++ .../kube-object/src/specifics/role-binding.ts | 42 + packages/kube-object/src/specifics/role.ts | 31 + .../src/specifics/runtime-class.ts | 65 ++ packages/kube-object/src/specifics/secret.ts | 68 ++ .../specifics/self-subject-rules-reviews.ts | 66 ++ .../src/specifics/service-account.ts | 49 + packages/kube-object/src/specifics/service.ts | 139 +++ .../kube-object/src/specifics/stateful-set.ts | 69 ++ .../src/specifics/storage-class.ts | 87 ++ .../validating-webhook-configuration.ts | 32 + .../src/specifics/vertical-pod-autoscaler.ts | 142 +++ .../src}/types/aggregation-rule.ts | 2 +- .../src}/types/capabilities.ts | 0 .../src}/types/condition.ts | 0 .../src}/types/container-port.ts | 0 .../src}/types/container.ts | 0 .../types/cross-version-object-reference.ts | 1 - .../src}/types/env-from-source.ts | 0 .../src}/types/env-source.ts | 2 +- .../src}/types/env-var-key-selector.ts | 0 .../src}/types/env-var-source.ts | 0 .../src}/types/env-var.ts | 0 .../src}/types/exec-action.ts | 0 .../src}/types/external-documentation.ts | 0 .../src}/types/handler.ts | 0 .../src}/types/http-get-action.ts | 0 .../src}/types/http-header.ts | 0 .../src}/types/index.ts | 0 .../src}/types/job-template-spec.ts | 4 +- .../src}/types/json-schema-props.ts | 39 +- .../src}/types/lifecycle.ts | 0 .../src}/types/object-field-selector.ts | 0 .../persistent-volume-claim-template-spec.ts | 4 +- .../src}/types/pod-security-context.ts | 3 +- .../src}/types/pod-template-spec.ts | 4 +- .../src}/types/policy-rule.ts | 0 .../src}/types/preemption-policy.ts | 0 .../src}/types/probe.ts | 3 +- .../src}/types/resource-field-selector.ts | 0 .../src}/types/resource-requirements.ts | 8 +- .../src}/types/role-ref.ts | 0 .../src}/types/se-linux-options.ts | 0 .../src}/types/seccomp-profile.ts | 0 .../kube-object/src/types/security-context.ts | 78 ++ .../src}/types/subject.ts | 0 .../src}/types/tcp-socket-action.ts | 0 .../src}/types/volume-device.ts | 0 .../src}/types/volume-mount.ts | 0 .../types/windows-security-context-options.ts | 5 +- packages/kube-object/src/utils.ts | 113 +++ packages/kube-object/tsconfig.json | 4 + packages/kube-object/webpack.config.js | 1 + 466 files changed, 6840 insertions(+), 6377 deletions(-) rename packages/core/src/common/front-end-routing/routes/cluster/workloads/{replicationcontrollers/navigate-to-replication-controllers.injectable.ts => replication-controllers/navigate-to.injectable.ts} (87%) rename packages/core/src/common/front-end-routing/routes/cluster/workloads/{replicationcontrollers/replicationcontrollers-route.injectable.ts => replication-controllers/route.injectable.ts} (91%) delete mode 100644 packages/core/src/common/k8s-api/endpoints/types/security-context.ts delete mode 100644 packages/core/src/common/k8s-api/kube-object.ts rename packages/core/src/renderer/components/storage-volumes/{get-persisten-volumes-by-storage-class.injectable.ts => get-persistent-volumes-by-storage-class.injectable.ts} (89%) rename packages/core/src/renderer/components/{workloads-replicationcontrollers => workloads-replication-controllers}/index.ts (61%) rename packages/core/src/renderer/components/{workloads-replicationcontrollers/replicationcontroller-details.module.scss => workloads-replication-controllers/replication-controller-details.module.scss} (100%) rename packages/core/src/renderer/components/{workloads-replicationcontrollers/replicationcontroller-details.tsx => workloads-replication-controllers/replication-controller-details.tsx} (91%) rename packages/core/src/renderer/components/{workloads-replicationcontrollers/replicationcontroller-sidebar-items.injectable.tsx => workloads-replication-controllers/replication-controller-sidebar-items.injectable.tsx} (75%) rename packages/core/src/renderer/components/{workloads-replicationcontrollers/replicationcontroller-store.injectable.ts => workloads-replication-controllers/replication-controller-store.injectable.ts} (83%) create mode 100644 packages/core/src/renderer/components/workloads-replication-controllers/replication-controller-store.ts rename packages/core/src/renderer/components/{workloads-replicationcontrollers => workloads-replication-controllers}/replication-controllers-route-component.injectable.ts (56%) rename packages/core/src/renderer/components/{workloads-replicationcontrollers/replicationcontrollers.module.scss => workloads-replication-controllers/replication-controllers.module.scss} (100%) create mode 100644 packages/core/src/renderer/components/workloads-replication-controllers/replication-controllers.tsx delete mode 100644 packages/core/src/renderer/components/workloads-replicationcontrollers/replicationcontroller-store.ts delete mode 100644 packages/core/src/renderer/components/workloads-replicationcontrollers/replicationcontrollers.tsx create mode 100644 packages/kube-object/.eslintrc.js create mode 100644 packages/kube-object/.prettierrc create mode 100644 packages/kube-object/index.ts create mode 100644 packages/kube-object/jest.config.js create mode 100644 packages/kube-object/package.json create mode 100644 packages/kube-object/src/api-types.ts create mode 100644 packages/kube-object/src/kube-object.ts create mode 100644 packages/kube-object/src/kube-status.ts create mode 100644 packages/kube-object/src/specifics/cluster-role-binding.ts create mode 100644 packages/kube-object/src/specifics/cluster-role.ts create mode 100644 packages/kube-object/src/specifics/cluster.ts create mode 100644 packages/kube-object/src/specifics/component-status.ts create mode 100644 packages/kube-object/src/specifics/config-map.ts create mode 100644 packages/kube-object/src/specifics/cron-job.ts create mode 100644 packages/kube-object/src/specifics/custom-resource-definition.ts create mode 100644 packages/kube-object/src/specifics/daemon-set.ts create mode 100644 packages/kube-object/src/specifics/deployment.ts create mode 100644 packages/kube-object/src/specifics/endpoint.ts create mode 100644 packages/kube-object/src/specifics/events.ts create mode 100644 packages/kube-object/src/specifics/horizontal-pod-autoscaler.ts create mode 100644 packages/kube-object/src/specifics/index.ts create mode 100644 packages/kube-object/src/specifics/ingress-class.ts create mode 100644 packages/kube-object/src/specifics/ingress.ts create mode 100644 packages/kube-object/src/specifics/job.ts create mode 100644 packages/kube-object/src/specifics/lease.ts create mode 100644 packages/kube-object/src/specifics/limit-range.ts create mode 100644 packages/kube-object/src/specifics/mutating-webhook-configuration.ts create mode 100644 packages/kube-object/src/specifics/namespace.ts create mode 100644 packages/kube-object/src/specifics/network-policy.ts rename packages/{core/src/common/k8s-api/__tests__ => kube-object/src/specifics}/node.test.ts (96%) create mode 100644 packages/kube-object/src/specifics/node.ts create mode 100644 packages/kube-object/src/specifics/persistent-volume-claim.ts create mode 100644 packages/kube-object/src/specifics/persistent-volume.ts create mode 100644 packages/kube-object/src/specifics/pod-disruption-budget.ts create mode 100644 packages/kube-object/src/specifics/pod-metrics.ts create mode 100644 packages/kube-object/src/specifics/pod-security-policy.ts rename packages/{core/src/common/k8s-api/__tests__/pods.test.ts => kube-object/src/specifics/pod.test.ts} (73%) create mode 100644 packages/kube-object/src/specifics/pod.ts create mode 100644 packages/kube-object/src/specifics/priority-class.ts create mode 100644 packages/kube-object/src/specifics/replica-set.ts create mode 100644 packages/kube-object/src/specifics/replication-controller.ts create mode 100644 packages/kube-object/src/specifics/resource-quota.ts create mode 100644 packages/kube-object/src/specifics/role-binding.ts create mode 100644 packages/kube-object/src/specifics/role.ts create mode 100644 packages/kube-object/src/specifics/runtime-class.ts create mode 100644 packages/kube-object/src/specifics/secret.ts create mode 100644 packages/kube-object/src/specifics/self-subject-rules-reviews.ts create mode 100644 packages/kube-object/src/specifics/service-account.ts create mode 100644 packages/kube-object/src/specifics/service.ts create mode 100644 packages/kube-object/src/specifics/stateful-set.ts create mode 100644 packages/kube-object/src/specifics/storage-class.ts create mode 100644 packages/kube-object/src/specifics/validating-webhook-configuration.ts create mode 100644 packages/kube-object/src/specifics/vertical-pod-autoscaler.ts rename packages/{core/src/common/k8s-api/endpoints => kube-object/src}/types/aggregation-rule.ts (80%) rename packages/{core/src/common/k8s-api/endpoints => kube-object/src}/types/capabilities.ts (100%) rename packages/{core/src/common/k8s-api/endpoints => kube-object/src}/types/condition.ts (100%) rename packages/{core/src/common/k8s-api/endpoints => kube-object/src}/types/container-port.ts (100%) rename packages/{core/src/common/k8s-api/endpoints => kube-object/src}/types/container.ts (100%) rename packages/{core/src/common/k8s-api/endpoints => kube-object/src}/types/cross-version-object-reference.ts (99%) rename packages/{core/src/common/k8s-api/endpoints => kube-object/src}/types/env-from-source.ts (100%) rename packages/{core/src/common/k8s-api/endpoints => kube-object/src}/types/env-source.ts (81%) rename packages/{core/src/common/k8s-api/endpoints => kube-object/src}/types/env-var-key-selector.ts (100%) rename packages/{core/src/common/k8s-api/endpoints => kube-object/src}/types/env-var-source.ts (100%) rename packages/{core/src/common/k8s-api/endpoints => kube-object/src}/types/env-var.ts (100%) rename packages/{core/src/common/k8s-api/endpoints => kube-object/src}/types/exec-action.ts (100%) rename packages/{core/src/common/k8s-api/endpoints => kube-object/src}/types/external-documentation.ts (100%) rename packages/{core/src/common/k8s-api/endpoints => kube-object/src}/types/handler.ts (100%) rename packages/{core/src/common/k8s-api/endpoints => kube-object/src}/types/http-get-action.ts (100%) rename packages/{core/src/common/k8s-api/endpoints => kube-object/src}/types/http-header.ts (100%) rename packages/{core/src/common/k8s-api/endpoints => kube-object/src}/types/index.ts (100%) rename packages/{core/src/common/k8s-api/endpoints => kube-object/src}/types/job-template-spec.ts (83%) rename packages/{core/src/common/k8s-api/endpoints => kube-object/src}/types/json-schema-props.ts (78%) rename packages/{core/src/common/k8s-api/endpoints => kube-object/src}/types/lifecycle.ts (100%) rename packages/{core/src/common/k8s-api/endpoints => kube-object/src}/types/object-field-selector.ts (100%) rename packages/{core/src/common/k8s-api/endpoints => kube-object/src}/types/persistent-volume-claim-template-spec.ts (79%) rename packages/{core/src/common/k8s-api/endpoints => kube-object/src}/types/pod-security-context.ts (93%) rename packages/{core/src/common/k8s-api/endpoints => kube-object/src}/types/pod-template-spec.ts (83%) rename packages/{core/src/common/k8s-api/endpoints => kube-object/src}/types/policy-rule.ts (100%) rename packages/{core/src/common/k8s-api/endpoints => kube-object/src}/types/preemption-policy.ts (100%) rename packages/{core/src/common/k8s-api/endpoints => kube-object/src}/types/probe.ts (97%) rename packages/{core/src/common/k8s-api/endpoints => kube-object/src}/types/resource-field-selector.ts (100%) rename packages/{core/src/common/k8s-api/endpoints => kube-object/src}/types/resource-requirements.ts (79%) rename packages/{core/src/common/k8s-api/endpoints => kube-object/src}/types/role-ref.ts (100%) rename packages/{core/src/common/k8s-api/endpoints => kube-object/src}/types/se-linux-options.ts (100%) rename packages/{core/src/common/k8s-api/endpoints => kube-object/src}/types/seccomp-profile.ts (100%) create mode 100644 packages/kube-object/src/types/security-context.ts rename packages/{core/src/common/k8s-api/endpoints => kube-object/src}/types/subject.ts (100%) rename packages/{core/src/common/k8s-api/endpoints => kube-object/src}/types/tcp-socket-action.ts (100%) rename packages/{core/src/common/k8s-api/endpoints => kube-object/src}/types/volume-device.ts (100%) rename packages/{core/src/common/k8s-api/endpoints => kube-object/src}/types/volume-mount.ts (100%) rename packages/{core/src/common/k8s-api/endpoints => kube-object/src}/types/windows-security-context-options.ts (84%) create mode 100644 packages/kube-object/src/utils.ts create mode 100644 packages/kube-object/tsconfig.json create mode 100644 packages/kube-object/webpack.config.js diff --git a/packages/core/src/common/front-end-routing/routes/cluster/workloads/replicationcontrollers/navigate-to-replication-controllers.injectable.ts b/packages/core/src/common/front-end-routing/routes/cluster/workloads/replication-controllers/navigate-to.injectable.ts similarity index 87% rename from packages/core/src/common/front-end-routing/routes/cluster/workloads/replicationcontrollers/navigate-to-replication-controllers.injectable.ts rename to packages/core/src/common/front-end-routing/routes/cluster/workloads/replication-controllers/navigate-to.injectable.ts index 240ad0f37e..9158c163d7 100644 --- a/packages/core/src/common/front-end-routing/routes/cluster/workloads/replicationcontrollers/navigate-to-replication-controllers.injectable.ts +++ b/packages/core/src/common/front-end-routing/routes/cluster/workloads/replication-controllers/navigate-to.injectable.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import replicationControllersRouteInjectable from "./replicationcontrollers-route.injectable"; +import replicationControllersRouteInjectable from "./route.injectable"; import { navigateToRouteInjectionToken } from "../../../../navigate-to-route-injection-token"; const navigateToReplicationControllersInjectable = getInjectable({ diff --git a/packages/core/src/common/front-end-routing/routes/cluster/workloads/replicationcontrollers/replicationcontrollers-route.injectable.ts b/packages/core/src/common/front-end-routing/routes/cluster/workloads/replication-controllers/route.injectable.ts similarity index 91% rename from packages/core/src/common/front-end-routing/routes/cluster/workloads/replicationcontrollers/replicationcontrollers-route.injectable.ts rename to packages/core/src/common/front-end-routing/routes/cluster/workloads/replication-controllers/route.injectable.ts index c6ce4afafe..e43712632f 100644 --- a/packages/core/src/common/front-end-routing/routes/cluster/workloads/replicationcontrollers/replicationcontrollers-route.injectable.ts +++ b/packages/core/src/common/front-end-routing/routes/cluster/workloads/replication-controllers/route.injectable.ts @@ -7,10 +7,10 @@ import { shouldShowResourceInjectionToken } from "../../../../../../features/clu import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const replicationControllersRouteInjectable = getInjectable({ - id: "replicationcontrollers-route", + id: "replication-controllers-route", instantiate: (di) => ({ - path: "/replicationcontrollers", + path: "/replication-controllers", clusterFrame: true, isEnabled: di.inject(shouldShowResourceInjectionToken, { apiName: "replicationcontrollers", diff --git a/packages/core/src/common/k8s-api/__tests__/api-manager.test.ts b/packages/core/src/common/k8s-api/__tests__/api-manager.test.ts index 0490f446fe..f4d93240f3 100644 --- a/packages/core/src/common/k8s-api/__tests__/api-manager.test.ts +++ b/packages/core/src/common/k8s-api/__tests__/api-manager.test.ts @@ -15,7 +15,7 @@ import loggerInjectable from "../../logger.injectable"; import type { ApiManager } from "../api-manager"; import apiManagerInjectable from "../api-manager/manager.injectable"; import { KubeApi } from "../kube-api"; -import { KubeObject } from "../kube-object"; +import { KubeObject } from "@k8slens/kube-object"; import { KubeObjectStore } from "../kube-object.store"; import maybeKubeApiInjectable from "../maybe-kube-api.injectable"; @@ -27,8 +27,8 @@ import { customResourceDefinitionApiInjectionToken } from "../api-manager/crd-ap import assert from "assert"; class TestApi extends KubeApi { - protected async checkPreferredVersion() { - return; + protected checkPreferredVersion() { + return Promise.resolve(); } } diff --git a/packages/core/src/common/k8s-api/__tests__/crd.test.ts b/packages/core/src/common/k8s-api/__tests__/crd.test.ts index e1538dfbe6..0942e637c0 100644 --- a/packages/core/src/common/k8s-api/__tests__/crd.test.ts +++ b/packages/core/src/common/k8s-api/__tests__/crd.test.ts @@ -3,8 +3,8 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import type { CustomResourceDefinitionSpec } from "../endpoints"; -import { CustomResourceDefinition } from "../endpoints"; +import type { CustomResourceDefinitionSpec } from "@k8slens/kube-object"; +import { CustomResourceDefinition } from "@k8slens/kube-object"; describe("Crds", () => { describe("getVersion()", () => { diff --git a/packages/core/src/common/k8s-api/__tests__/deployment.api.test.ts b/packages/core/src/common/k8s-api/__tests__/deployment.api.test.ts index 96758a5591..00bf87c4e0 100644 --- a/packages/core/src/common/k8s-api/__tests__/deployment.api.test.ts +++ b/packages/core/src/common/k8s-api/__tests__/deployment.api.test.ts @@ -33,7 +33,7 @@ describe("DeploymentApi", () => { describe("scale", () => { it("requests Kubernetes API with PATCH verb and correct amount of replicas", () => { - deploymentApi.scale({ namespace: "default", name: "deployment-1" }, 5); + void deploymentApi.scale({ namespace: "default", name: "deployment-1" }, 5); expect(kubeJsonApi.patch).toHaveBeenCalledWith("/apis/apps/v1/namespaces/default/deployments/deployment-1/scale", { data: { diff --git a/packages/core/src/common/k8s-api/__tests__/endpoint.api.test.ts b/packages/core/src/common/k8s-api/__tests__/endpoint.api.test.ts index 666c5df63a..56144873c5 100644 --- a/packages/core/src/common/k8s-api/__tests__/endpoint.api.test.ts +++ b/packages/core/src/common/k8s-api/__tests__/endpoint.api.test.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import { formatEndpointSubset } from "../endpoints"; +import { formatEndpointSubset } from "@k8slens/kube-object"; describe("endpoint tests", () => { describe("EndpointSubset", () => { diff --git a/packages/core/src/common/k8s-api/__tests__/ingress.api.test.ts b/packages/core/src/common/k8s-api/__tests__/ingress.api.test.ts index acb2a59a68..96c2dfce7a 100644 --- a/packages/core/src/common/k8s-api/__tests__/ingress.api.test.ts +++ b/packages/core/src/common/k8s-api/__tests__/ingress.api.test.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import { computeRuleDeclarations, Ingress } from "../endpoints"; +import { computeRuleDeclarations, Ingress } from "@k8slens/kube-object"; describe("Ingress", () => { it("given no loadbalancer ingresses in status property, loadbalancers should be an empty array", () => { diff --git a/packages/core/src/common/k8s-api/__tests__/kube-api-parse.test.ts b/packages/core/src/common/k8s-api/__tests__/kube-api-parse.test.ts index 6d05385a28..1783bdb59b 100644 --- a/packages/core/src/common/k8s-api/__tests__/kube-api-parse.test.ts +++ b/packages/core/src/common/k8s-api/__tests__/kube-api-parse.test.ts @@ -3,7 +3,6 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -jest.mock("../kube-object"); jest.mock("../kube-api"); jest.mock("../api-manager", () => ({ apiManager() { diff --git a/packages/core/src/common/k8s-api/__tests__/kube-api-version-detection.test.ts b/packages/core/src/common/k8s-api/__tests__/kube-api-version-detection.test.ts index e6efa606b9..20475205fe 100644 --- a/packages/core/src/common/k8s-api/__tests__/kube-api-version-detection.test.ts +++ b/packages/core/src/common/k8s-api/__tests__/kube-api-version-detection.test.ts @@ -4,7 +4,8 @@ */ import type { ApiManager } from "../api-manager"; import type { IngressApi } from "../endpoints"; -import { Ingress, HorizontalPodAutoscalerApi } from "../endpoints"; +import { HorizontalPodAutoscalerApi } from "../endpoints"; +import { Ingress } from "@k8slens/kube-object"; import { getDiForUnitTesting } from "../../../renderer/getDiForUnitTesting"; import type { Fetch } from "../../fetch/fetch.injectable"; import fetchInjectable from "../../fetch/fetch.injectable"; diff --git a/packages/core/src/common/k8s-api/__tests__/kube-api.test.ts b/packages/core/src/common/k8s-api/__tests__/kube-api.test.ts index a5cce9f02c..6e5c1f507a 100644 --- a/packages/core/src/common/k8s-api/__tests__/kube-api.test.ts +++ b/packages/core/src/common/k8s-api/__tests__/kube-api.test.ts @@ -4,38 +4,27 @@ */ import type { KubeApiWatchCallback } from "../kube-api"; import { KubeApi } from "../kube-api"; -import type { KubeJsonApiData } from "../kube-json-api"; import { PassThrough } from "stream"; -import type { DeploymentApi, NamespaceApi } from "../endpoints"; -import { Deployment, Pod, PodApi } from "../endpoints"; +import { PodApi } from "../endpoints"; +import type { DeploymentApi, NamespaceApi } from "../endpoints"; +import { Deployment, Pod } from "@k8slens/kube-object"; import { getDiForUnitTesting } from "../../../renderer/getDiForUnitTesting"; import type { Fetch } from "../../fetch/fetch.injectable"; import fetchInjectable from "../../fetch/fetch.injectable"; -import type { - CreateKubeApiForRemoteCluster, -} from "../create-kube-api-for-remote-cluster.injectable"; -import createKubeApiForRemoteClusterInjectable - from "../create-kube-api-for-remote-cluster.injectable"; +import type { CreateKubeApiForRemoteCluster } from "../create-kube-api-for-remote-cluster.injectable"; +import createKubeApiForRemoteClusterInjectable from "../create-kube-api-for-remote-cluster.injectable"; import type { AsyncFnMock } from "@async-fn/jest"; import asyncFn from "@async-fn/jest"; import { flushPromises } from "@k8slens/test-utils"; import createKubeJsonApiInjectable from "../create-kube-json-api.injectable"; import type { IKubeWatchEvent } from "../kube-watch-event"; -import type { KubeJsonApiDataFor, KubeStatusData } from "../kube-object"; -import setupAutoRegistrationInjectable - from "../../../renderer/before-frame-starts/runnables/setup-auto-registration.injectable"; -import { - createMockResponseFromStream, - createMockResponseFromString, -} from "../../../test-utils/mock-responses"; -import storesAndApisCanBeCreatedInjectable - from "../../../renderer/stores-apis-can-be-created.injectable"; -import directoryForUserDataInjectable - from "../../app-paths/directory-for-user-data/directory-for-user-data.injectable"; -import hostedClusterInjectable - from "../../../renderer/cluster-frame-context/hosted-cluster.injectable"; -import directoryForKubeConfigsInjectable - from "../../app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable"; +import type { KubeJsonApiDataFor, KubeStatusData, KubeJsonApiData } from "@k8slens/kube-object"; +import setupAutoRegistrationInjectable from "../../../renderer/before-frame-starts/runnables/setup-auto-registration.injectable"; +import { createMockResponseFromStream, createMockResponseFromString } from "../../../test-utils/mock-responses"; +import storesAndApisCanBeCreatedInjectable from "../../../renderer/stores-apis-can-be-created.injectable"; +import directoryForUserDataInjectable from "../../app-paths/directory-for-user-data/directory-for-user-data.injectable"; +import hostedClusterInjectable from "../../../renderer/cluster-frame-context/hosted-cluster.injectable"; +import directoryForKubeConfigsInjectable from "../../app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable"; import apiKubeInjectable from "../../../renderer/k8s/api-kube.injectable"; import type { DiContainer } from "@ogre-tools/injectable"; import deploymentApiInjectable from "../endpoints/deployment.api.injectable"; @@ -51,7 +40,7 @@ describe("createKubeApiForRemoteCluster", () => { let createKubeApiForRemoteCluster: CreateKubeApiForRemoteCluster; let fetchMock: AsyncFnMock; - beforeEach(async () => { + beforeEach(() => { const di = getDiForUnitTesting(); di.override(directoryForUserDataInjectable, () => "/some-user-store-path"); @@ -70,7 +59,7 @@ describe("createKubeApiForRemoteCluster", () => { createKubeApiForRemoteCluster = di.inject(createKubeApiForRemoteClusterInjectable); }); - it("builds api client for KubeObject", async () => { + it("builds api client for KubeObject", () => { const api = createKubeApiForRemoteCluster({ cluster: { server: "https://127.0.0.1:6443", @@ -150,7 +139,7 @@ describe("KubeApi", () => { let fetchMock: AsyncFnMock; let di: DiContainer; - beforeEach(async () => { + beforeEach(() => { di = getDiForUnitTesting(); di.override(directoryForUserDataInjectable, () => "/some-user-store-path"); @@ -374,7 +363,7 @@ describe("KubeApi", () => { describe("when request resolves", () => { beforeEach(async () => { - fetchMock.resolveSpecific( + await fetchMock.resolveSpecific( ["http://127.0.0.1:9999/api-kube/api/v1/namespaces/default/pods/foo?propagationPolicy=Background"], createMockResponseFromString("http://127.0.0.1:9999/api-kube/api/v1/namespaces/default/pods/foo?propagationPolicy=Background", "{}"), ); @@ -410,7 +399,7 @@ describe("KubeApi", () => { describe("when request resolves", () => { beforeEach(async () => { - fetchMock.resolveSpecific( + await fetchMock.resolveSpecific( ["http://127.0.0.1:9999/api-kube/api/v1/namespaces/default/pods/foo?propagationPolicy=Background"], createMockResponseFromString("http://127.0.0.1:9999/api-kube/api/v1/namespaces/default/pods/foo?propagationPolicy=Background", "{}"), ); @@ -446,7 +435,7 @@ describe("KubeApi", () => { describe("when request resolves", () => { beforeEach(async () => { - fetchMock.resolveSpecific( + await fetchMock.resolveSpecific( ["http://127.0.0.1:9999/api-kube/api/v1/namespaces/test/pods/foo?propagationPolicy=Background"], createMockResponseFromString("http://127.0.0.1:9999/api-kube/api/v1/namespaces/test/pods/foo?propagationPolicy=Background", "{}"), ); @@ -461,7 +450,7 @@ describe("KubeApi", () => { describe("eviction-api as better replacement for pod.delete() request", () => { let evictRequest: Promise; - beforeEach(async () => { + beforeEach(() => { evictRequest = api.evict({ name: "foo", namespace: "test" }); }); @@ -478,7 +467,7 @@ describe("KubeApi", () => { }); it("should resolve the call with >=200 <300 http code", async () => { - fetchMock.resolveSpecific( + await fetchMock.resolveSpecific( ["http://127.0.0.1:9999/api-kube/api/v1/namespaces/test/pods/foo/eviction"], createMockResponseFromString("http://127.0.0.1:9999/api-kube/api/v1/namespaces/test/pods/foo/eviction", JSON.stringify({ apiVersion: "policy/v1", @@ -492,7 +481,7 @@ describe("KubeApi", () => { }); it("should throw in case of error", async () => { - fetchMock.resolveSpecific( + await fetchMock.resolveSpecific( ["http://127.0.0.1:9999/api-kube/api/v1/namespaces/test/pods/foo/eviction"], createMockResponseFromString("http://127.0.0.1:9999/api-kube/api/v1/namespaces/test/pods/foo/eviction", JSON.stringify({ apiVersion: "policy/v1", @@ -502,12 +491,12 @@ describe("KubeApi", () => { } as KubeStatusData)), ); - expect(async () => evictRequest).rejects.toBe("500: something went wrong"); + await expect(async () => evictRequest).rejects.toBe("500: something went wrong"); }); }); }); - describe("deleting namespaces (cluser scoped resource)", () => { + describe("deleting namespaces (cluster scoped resource)", () => { let api: NamespaceApi; beforeEach(() => { @@ -538,7 +527,7 @@ describe("KubeApi", () => { describe("when request resolves", () => { beforeEach(async () => { - fetchMock.resolveSpecific( + await fetchMock.resolveSpecific( ["http://127.0.0.1:9999/api-kube/api/v1/namespaces/foo?propagationPolicy=Background"], createMockResponseFromString("http://127.0.0.1:9999/api-kube/api/v1/namespaces/foo?propagationPolicy=Background", "{}"), ); @@ -574,7 +563,7 @@ describe("KubeApi", () => { describe("when request resolves", () => { beforeEach(async () => { - fetchMock.resolveSpecific( + await fetchMock.resolveSpecific( ["http://127.0.0.1:9999/api-kube/api/v1/namespaces/foo?propagationPolicy=Background"], createMockResponseFromString("http://127.0.0.1:9999/api-kube/api/v1/namespaces/foo?propagationPolicy=Background", "{}"), ); @@ -587,8 +576,8 @@ describe("KubeApi", () => { }); describe("when deleting by name and namespace", () => { - it("rejects request", () => { - expect(api.delete({ name: "foo", namespace: "test" })).rejects.toBeDefined(); + it("rejects request", async () => { + await expect(api.delete({ name: "foo", namespace: "test" })).rejects.toBeDefined(); }); }); }); diff --git a/packages/core/src/common/k8s-api/__tests__/kube-object.store.test.ts b/packages/core/src/common/k8s-api/__tests__/kube-object.store.test.ts index 24820648af..62d5a83e55 100644 --- a/packages/core/src/common/k8s-api/__tests__/kube-object.store.test.ts +++ b/packages/core/src/common/k8s-api/__tests__/kube-object.store.test.ts @@ -5,7 +5,7 @@ import { noop } from "@k8slens/utilities"; import type { KubeApi } from "../kube-api"; -import { KubeObject } from "../kube-object"; +import { KubeObject } from "@k8slens/kube-object"; import type { KubeObjectStoreLoadingParams } from "../kube-object.store"; import { KubeObjectStore } from "../kube-object.store"; @@ -30,7 +30,7 @@ class FakeKubeObjectStore extends KubeObjectStore { } async loadItems(params: KubeObjectStoreLoadingParams) { - return this._loadItems(params); + return Promise.resolve(this._loadItems(params)); } } diff --git a/packages/core/src/common/k8s-api/__tests__/kube-object.test.ts b/packages/core/src/common/k8s-api/__tests__/kube-object.test.ts index 4d5865c5db..5d458f7446 100644 --- a/packages/core/src/common/k8s-api/__tests__/kube-object.test.ts +++ b/packages/core/src/common/k8s-api/__tests__/kube-object.test.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import { KubeObject } from "../kube-object"; +import { KubeObject } from "@k8slens/kube-object"; describe("KubeObject", () => { describe("isJsonApiData", () => { diff --git a/packages/core/src/common/k8s-api/__tests__/pods.api.test.ts b/packages/core/src/common/k8s-api/__tests__/pods.api.test.ts index 9b01dff73b..5f6ad20498 100644 --- a/packages/core/src/common/k8s-api/__tests__/pods.api.test.ts +++ b/packages/core/src/common/k8s-api/__tests__/pods.api.test.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import { Pod } from "../endpoints"; +import { Pod } from "@k8slens/kube-object"; describe("Pod tests", () => { it("getAllContainers() should never throw", () => { diff --git a/packages/core/src/common/k8s-api/api-manager/api-manager.ts b/packages/core/src/common/k8s-api/api-manager/api-manager.ts index 8e0eff8255..de8a9090ad 100644 --- a/packages/core/src/common/k8s-api/api-manager/api-manager.ts +++ b/packages/core/src/common/k8s-api/api-manager/api-manager.ts @@ -8,7 +8,7 @@ import type { KubeObjectStore } from "../kube-object.store"; import type { IComputedValue } from "mobx"; import { autorun, action, observable } from "mobx"; import type { KubeApi } from "../kube-api"; -import type { KubeObject, ObjectReference } from "../kube-object"; +import type { KubeObject, ObjectReference } from "@k8slens/kube-object"; import { parseKubeApi, createKubeApiURL } from "../kube-api-parse"; import { getOrInsertWith, iter } from "@k8slens/utilities"; import type { CreateCustomResourceStore } from "./create-custom-resource-store.injectable"; diff --git a/packages/core/src/common/k8s-api/api-manager/create-custom-resource-store.injectable.ts b/packages/core/src/common/k8s-api/api-manager/create-custom-resource-store.injectable.ts index fbf46e2b42..a12ef7eb2e 100644 --- a/packages/core/src/common/k8s-api/api-manager/create-custom-resource-store.injectable.ts +++ b/packages/core/src/common/k8s-api/api-manager/create-custom-resource-store.injectable.ts @@ -6,7 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable"; import clusterFrameContextForNamespacedResourcesInjectable from "../../../renderer/cluster-frame-context/for-namespaced-resources.injectable"; import loggerInjectable from "../../logger.injectable"; import type { KubeApi } from "../kube-api"; -import type { KubeObject } from "../kube-object"; +import type { KubeObject } from "@k8slens/kube-object"; import type { KubeObjectStoreDependencies } from "../kube-object.store"; import { CustomResourceStore } from "./resource.store"; diff --git a/packages/core/src/common/k8s-api/api-manager/resource.store.ts b/packages/core/src/common/k8s-api/api-manager/resource.store.ts index c81ce7daec..c15d8970e3 100644 --- a/packages/core/src/common/k8s-api/api-manager/resource.store.ts +++ b/packages/core/src/common/k8s-api/api-manager/resource.store.ts @@ -6,7 +6,7 @@ import type { KubeApi } from "../kube-api"; import type { KubeObjectStoreDependencies } from "../kube-object.store"; import { KubeObjectStore } from "../kube-object.store"; -import type { KubeObject } from "../kube-object"; +import type { KubeObject } from "@k8slens/kube-object"; export class CustomResourceStore extends KubeObjectStore> { constructor(deps: KubeObjectStoreDependencies, api: KubeApi) { diff --git a/packages/core/src/common/k8s-api/create-kube-api-for-cluster.injectable.ts b/packages/core/src/common/k8s-api/create-kube-api-for-cluster.injectable.ts index dc3a3fef69..62509b5fe4 100644 --- a/packages/core/src/common/k8s-api/create-kube-api-for-cluster.injectable.ts +++ b/packages/core/src/common/k8s-api/create-kube-api-for-cluster.injectable.ts @@ -10,7 +10,7 @@ import apiBaseInjectable from "./api-base.injectable"; import type { KubeApiConstructor } from "./create-kube-api-for-remote-cluster.injectable"; import createKubeJsonApiInjectable from "./create-kube-json-api.injectable"; import { KubeApi } from "./kube-api"; -import type { KubeJsonApiDataFor, KubeObject, KubeObjectConstructor } from "./kube-object"; +import type { KubeJsonApiDataFor, KubeObject, KubeObjectConstructor } from "@k8slens/kube-object"; export interface CreateKubeApiForLocalClusterConfig { metadata: { diff --git a/packages/core/src/common/k8s-api/create-kube-api-for-remote-cluster.injectable.ts b/packages/core/src/common/k8s-api/create-kube-api-for-remote-cluster.injectable.ts index bd7d2b1630..f4b98ddb44 100644 --- a/packages/core/src/common/k8s-api/create-kube-api-for-remote-cluster.injectable.ts +++ b/packages/core/src/common/k8s-api/create-kube-api-for-remote-cluster.injectable.ts @@ -11,7 +11,7 @@ import isDevelopmentInjectable from "../vars/is-development.injectable"; import createKubeJsonApiInjectable from "./create-kube-json-api.injectable"; import type { KubeApiOptions } from "./kube-api"; import { KubeApi } from "./kube-api"; -import type { KubeJsonApiDataFor, KubeObject, KubeObjectConstructor } from "./kube-object"; +import type { KubeJsonApiDataFor, KubeObject, KubeObjectConstructor } from "@k8slens/kube-object"; export interface CreateKubeApiForRemoteClusterConfig { cluster: { diff --git a/packages/core/src/common/k8s-api/endpoints/cluster-role-binding.api.ts b/packages/core/src/common/k8s-api/endpoints/cluster-role-binding.api.ts index 827a3ce95d..e328ba22e7 100644 --- a/packages/core/src/common/k8s-api/endpoints/cluster-role-binding.api.ts +++ b/packages/core/src/common/k8s-api/endpoints/cluster-role-binding.api.ts @@ -4,47 +4,8 @@ */ import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api"; import { KubeApi } from "../kube-api"; -import type { KubeJsonApiData } from "../kube-json-api"; -import type { ClusterScopedMetadata, KubeObjectMetadata, KubeObjectScope } from "../kube-object"; -import { KubeObject } from "../kube-object"; -import type { RoleRef } from "./types/role-ref"; -import type { Subject } from "./types/subject"; - -export interface ClusterRoleBindingData extends KubeJsonApiData, void, void> { - subjects?: Subject[]; - roleRef: RoleRef; -} - -export class ClusterRoleBinding extends KubeObject< - ClusterScopedMetadata, - void, - void -> { - static kind = "ClusterRoleBinding"; - static namespaced = false; - static apiBase = "/apis/rbac.authorization.k8s.io/v1/clusterrolebindings"; - - subjects?: Subject[]; - roleRef: RoleRef; - - constructor({ - subjects, - roleRef, - ...rest - }: ClusterRoleBindingData) { - super(rest); - this.subjects = subjects; - this.roleRef = roleRef; - } - - getSubjects() { - return this.subjects ?? []; - } - - getSubjectNames(): string { - return this.getSubjects().map(subject => subject.name).join(", "); - } -} +import type { ClusterRoleBindingData } from "@k8slens/kube-object"; +import { ClusterRoleBinding } from "@k8slens/kube-object"; export class ClusterRoleBindingApi extends KubeApi { constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions = {}) { diff --git a/packages/core/src/common/k8s-api/endpoints/cluster-role.api.ts b/packages/core/src/common/k8s-api/endpoints/cluster-role.api.ts index e55f934df9..a0b3220046 100644 --- a/packages/core/src/common/k8s-api/endpoints/cluster-role.api.ts +++ b/packages/core/src/common/k8s-api/endpoints/cluster-role.api.ts @@ -5,39 +5,8 @@ import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api"; import { KubeApi } from "../kube-api"; -import type { KubeJsonApiData } from "../kube-json-api"; -import type { ClusterScopedMetadata, KubeObjectMetadata, KubeObjectScope } from "../kube-object"; -import { KubeObject } from "../kube-object"; -import type { AggregationRule } from "./types/aggregation-rule"; -import type { PolicyRule } from "./types/policy-rule"; - -export interface ClusterRoleData extends KubeJsonApiData, void, void> { - rules?: PolicyRule[]; - aggregationRule?: AggregationRule; -} - -export class ClusterRole extends KubeObject< - ClusterScopedMetadata, - void, - void -> { - static kind = "ClusterRole"; - static namespaced = false; - static apiBase = "/apis/rbac.authorization.k8s.io/v1/clusterroles"; - - rules?: PolicyRule[]; - aggregationRule?: AggregationRule; - - constructor({ rules, aggregationRule, ...rest }: ClusterRoleData) { - super(rest); - this.rules = rules; - this.aggregationRule = aggregationRule; - } - - getRules() { - return this.rules || []; - } -} +import type { ClusterRoleData } from "@k8slens/kube-object"; +import { ClusterRole } from "@k8slens/kube-object"; export class ClusterRoleApi extends KubeApi { constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions = {}) { diff --git a/packages/core/src/common/k8s-api/endpoints/cluster.api.ts b/packages/core/src/common/k8s-api/endpoints/cluster.api.ts index 92f42f1e4e..36fe6b4a90 100644 --- a/packages/core/src/common/k8s-api/endpoints/cluster.api.ts +++ b/packages/core/src/common/k8s-api/endpoints/cluster.api.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import { KubeObject } from "../kube-object"; +import { Cluster } from "@k8slens/kube-object"; import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api"; import { KubeApi } from "../kube-api"; @@ -25,58 +25,3 @@ export class ClusterApi extends KubeApi { }); } } - -export enum ClusterStatus { - ACTIVE = "Active", - CREATING = "Creating", - REMOVING = "Removing", - ERROR = "Error", -} - -export interface Cluster { - spec: { - clusterNetwork?: { - serviceDomain?: string; - pods?: { - cidrBlocks?: string[]; - }; - services?: { - cidrBlocks?: string[]; - }; - }; - providerSpec: { - value: { - profile: string; - }; - }; - }; - status?: { - apiEndpoints: { - host: string; - port: string; - }[]; - providerStatus: { - adminUser?: string; - adminPassword?: string; - kubeconfig?: string; - processState?: string; - lensAddress?: string; - }; - errorMessage?: string; - errorReason?: string; - }; -} - -export class Cluster extends KubeObject { - static kind = "Cluster"; - static apiBase = "/apis/cluster.k8s.io/v1alpha1/clusters"; - static namespaced = true; - - getStatus() { - if (this.metadata.deletionTimestamp) return ClusterStatus.REMOVING; - if (!this.status || !this.status) return ClusterStatus.CREATING; - if (this.status.errorMessage) return ClusterStatus.ERROR; - - return ClusterStatus.ACTIVE; - } -} diff --git a/packages/core/src/common/k8s-api/endpoints/component-status.api.ts b/packages/core/src/common/k8s-api/endpoints/component-status.api.ts index a35fc92c44..d2076c9f32 100644 --- a/packages/core/src/common/k8s-api/endpoints/component-status.api.ts +++ b/packages/core/src/common/k8s-api/endpoints/component-status.api.ts @@ -3,30 +3,10 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import { KubeObject } from "../kube-object"; +import { ComponentStatus } from "@k8slens/kube-object"; import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api"; import { KubeApi } from "../kube-api"; -export interface ComponentStatusCondition { - type: string; - status: string; - message: string; -} - -export interface ComponentStatus { - conditions: ComponentStatusCondition[]; -} - -export class ComponentStatus extends KubeObject { - static kind = "ComponentStatus"; - static namespaced = false; - static apiBase = "/api/v1/componentstatuses"; - - getTruthyConditions() { - return this.conditions.filter(c => c.status === "True"); - } -} - export class ComponentStatusApi extends KubeApi { constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions = {}) { super(deps, { diff --git a/packages/core/src/common/k8s-api/endpoints/config-map.api.ts b/packages/core/src/common/k8s-api/endpoints/config-map.api.ts index cf635fb623..b8e0c589db 100644 --- a/packages/core/src/common/k8s-api/endpoints/config-map.api.ts +++ b/packages/core/src/common/k8s-api/endpoints/config-map.api.ts @@ -3,45 +3,10 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import type { KubeObjectMetadata, KubeObjectScope, NamespaceScopedMetadata } from "../kube-object"; -import { KubeObject } from "../kube-object"; -import type { KubeJsonApiData } from "../kube-json-api"; +import type { ConfigMapData } from "@k8slens/kube-object"; +import { ConfigMap } from "@k8slens/kube-object"; import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api"; import { KubeApi } from "../kube-api"; -import autoBind from "auto-bind"; - -export interface ConfigMapData extends KubeJsonApiData, void, void> { - data?: Partial>; - binaryData?: Partial>; - immutable?: boolean; -} - -export class ConfigMap extends KubeObject< - NamespaceScopedMetadata, - void, - void -> { - static kind = "ConfigMap"; - static namespaced = true; - static apiBase = "/api/v1/configmaps"; - - data: Partial>; - binaryData: Partial>; - immutable?: boolean; - - constructor({ data, binaryData, immutable, ...rest }: ConfigMapData) { - super(rest); - autoBind(this); - - this.data = data ?? {}; - this.binaryData = binaryData ?? {}; - this.immutable = immutable; - } - - getKeys(): string[] { - return Object.keys(this.data); - } -} export class ConfigMapApi extends KubeApi { constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) { diff --git a/packages/core/src/common/k8s-api/endpoints/cron-job.api.ts b/packages/core/src/common/k8s-api/endpoints/cron-job.api.ts index 1ad0c42511..4c61c259ac 100644 --- a/packages/core/src/common/k8s-api/endpoints/cron-job.api.ts +++ b/packages/core/src/common/k8s-api/endpoints/cron-job.api.ts @@ -3,13 +3,9 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import moment from "moment"; -import type { NamespaceScopedMetadata, ObjectReference } from "../kube-object"; -import { KubeObject } from "../kube-object"; -import { formatDuration } from "@k8slens/utilities"; -import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api"; +import { CronJob } from "@k8slens/kube-object"; +import type { DerivedKubeApiOptions, KubeApiDependencies, NamespacedResourceDescriptor } from "../kube-api"; import { KubeApi } from "../kube-api"; -import type { JobTemplateSpec } from "./types/job-template-spec"; export class CronJobApi extends KubeApi { constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions) { @@ -19,90 +15,19 @@ export class CronJobApi extends KubeApi { }); } - suspend(params: { namespace: string; name: string }) { - return this.request.patch(this.getUrl(params), { - data: { - spec: { - suspend: true, - }, + private requestSetSuspend(params: NamespacedResourceDescriptor, suspend: boolean) { + return this.patch(params, { + spec: { + suspend, }, - }, - { - headers: { - "content-type": "application/strategic-merge-patch+json", - }, - }); + }, "strategic"); } - resume(params: { namespace: string; name: string }) { - return this.request.patch(this.getUrl(params), { - data: { - spec: { - suspend: false, - }, - }, - }, - { - headers: { - "content-type": "application/strategic-merge-patch+json", - }, - }); - } -} - -export interface CronJobSpec { - concurrencyPolicy?: string; - failedJobsHistoryLimit?: number; - jobTemplate?: JobTemplateSpec; - schedule: string; - startingDeadlineSeconds?: number; - successfulJobsHistoryLimit?: number; - suspend?: boolean; -} - -export interface CronJobStatus { - lastScheduleTime?: string; - lastSuccessfulTime?: string; - active?: ObjectReference[]; -} - -export class CronJob extends KubeObject< - NamespaceScopedMetadata, - CronJobStatus, - CronJobSpec -> { - static readonly kind = "CronJob"; - static readonly namespaced = true; - static readonly apiBase = "/apis/batch/v1/cronjobs"; - - getSuspendFlag() { - return (this.spec.suspend ?? false).toString(); - } - - getLastScheduleTime() { - if (!this.status?.lastScheduleTime) return "-"; - const diff = moment().diff(this.status.lastScheduleTime); - - return formatDuration(diff, true); - } - - getSchedule() { - return this.spec.schedule; - } - - isNeverRun() { - const schedule = this.getSchedule(); - const daysInMonth = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; - const stamps = schedule.split(" "); - const day = Number(stamps[stamps.length - 3]); // 1-31 - const month = Number(stamps[stamps.length - 2]); // 1-12 - - if (schedule.startsWith("@")) return false; - - return day > daysInMonth[month - 1]; - } - - isSuspend() { - return this.spec.suspend; + suspend(params: NamespacedResourceDescriptor) { + return this.requestSetSuspend(params, true); + } + + resume(params: NamespacedResourceDescriptor) { + return this.requestSetSuspend(params, false); } } diff --git a/packages/core/src/common/k8s-api/endpoints/custom-resource-definition.api.ts b/packages/core/src/common/k8s-api/endpoints/custom-resource-definition.api.ts index 264c66cea3..0f304ad552 100644 --- a/packages/core/src/common/k8s-api/endpoints/custom-resource-definition.api.ts +++ b/packages/core/src/common/k8s-api/endpoints/custom-resource-definition.api.ts @@ -3,235 +3,9 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import { getLegacyGlobalDiForExtensionApi } from "@k8slens/legacy-global-di"; -import customResourcesRouteInjectable from "../../front-end-routing/routes/cluster/custom-resources/custom-resources/custom-resources-route.injectable"; -import { buildURL } from "@k8slens/utilities"; -import type { BaseKubeObjectCondition, ClusterScopedMetadata } from "../kube-object"; -import { KubeObject } from "../kube-object"; +import { CustomResourceDefinition } from "@k8slens/kube-object"; import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api"; import { KubeApi } from "../kube-api"; -import type { JSONSchemaProps } from "./types/json-schema-props"; - -interface AdditionalPrinterColumnsCommon { - name: string; - type: "integer" | "number" | "string" | "boolean" | "date"; - priority?: number; - format?: "int32" | "int64" | "float" | "double" | "byte" | "binary" | "date" | "date-time" | "password"; - description?: string; -} - -export type AdditionalPrinterColumnsV1 = AdditionalPrinterColumnsCommon & { - jsonPath: string; -}; - -type AdditionalPrinterColumnsV1Beta = AdditionalPrinterColumnsCommon & { - JSONPath: string; -}; - -export interface CustomResourceValidation { - openAPIV3Schema?: JSONSchemaProps; -} - -export interface CustomResourceDefinitionVersion { - name: string; - served: boolean; - storage: boolean; - schema?: CustomResourceValidation; // required in v1 but not present in v1beta - additionalPrinterColumns?: AdditionalPrinterColumnsV1[]; -} - -export interface CustomResourceDefinitionNames { - categories?: string[]; - kind: string; - listKind?: string; - plural: string; - shortNames?: string[]; - singular?: string; -} - -export interface CustomResourceConversion { - strategy?: string; - webhook?: WebhookConversion; -} - -export interface WebhookConversion { - clientConfig?: WebhookClientConfig[]; - conversionReviewVersions: string[]; -} - -export interface WebhookClientConfig { - caBundle?: string; - url?: string; - service?: ServiceReference; -} - -export interface ServiceReference { - name: string; - namespace: string; - path?: string; - port?: number; -} - -export interface CustomResourceDefinitionSpec { - group: string; - /** - * @deprecated for apiextensions.k8s.io/v1 but used in v1beta1 - */ - version?: string; - names: CustomResourceDefinitionNames; - scope: "Namespaced" | "Cluster"; - /** - * @deprecated for apiextensions.k8s.io/v1 but used in v1beta1 - */ - validation?: object; - versions?: CustomResourceDefinitionVersion[]; - conversion?: CustomResourceConversion; - /** - * @deprecated for apiextensions.k8s.io/v1 but used in v1beta1 - */ - additionalPrinterColumns?: AdditionalPrinterColumnsV1Beta[]; - preserveUnknownFields?: boolean; -} - -export interface CustomResourceDefinitionConditionAcceptedNames { - plural: string; - singular: string; - kind: string; - shortNames: string[]; - listKind: string; -} - -export interface CustomResourceDefinitionStatus { - conditions?: BaseKubeObjectCondition[]; - acceptedNames: CustomResourceDefinitionConditionAcceptedNames; - storedVersions: string[]; -} - -export class CustomResourceDefinition extends KubeObject< - ClusterScopedMetadata, - CustomResourceDefinitionStatus, - CustomResourceDefinitionSpec -> { - static kind = "CustomResourceDefinition"; - static namespaced = false; - static apiBase = "/apis/apiextensions.k8s.io/v1/customresourcedefinitions"; - - getResourceUrl() { - const di = getLegacyGlobalDiForExtensionApi(); - - const customResourcesRoute = di.inject(customResourcesRouteInjectable); - - return buildURL(customResourcesRoute.path, { - params: { - group: this.getGroup(), - name: this.getPluralName(), - }, - }); - } - - getResourceApiBase() { - const { group } = this.spec; - - return `/apis/${group}/${this.getVersion()}/${this.getPluralName()}`; - } - - getPluralName() { - return this.getNames().plural; - } - - getResourceKind() { - return this.spec.names.kind; - } - - getResourceTitle() { - const name = this.getPluralName(); - - return name[0].toUpperCase() + name.slice(1); - } - - getGroup() { - return this.spec.group; - } - - getScope() { - return this.spec.scope; - } - - getPreferedVersion(): CustomResourceDefinitionVersion { - const { apiVersion } = this; - - switch (apiVersion) { - case "apiextensions.k8s.io/v1": - for (const version of this.spec.versions ?? []) { - if (version.storage) { - return version; - } - } - break; - - case "apiextensions.k8s.io/v1beta1": { - const { additionalPrinterColumns: apc } = this.spec; - const additionalPrinterColumns = apc?.map(({ JSONPath, ...apc }) => ({ ...apc, jsonPath: JSONPath })); - - return { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - name: this.spec.version!, - served: true, - storage: true, - schema: this.spec.validation, - additionalPrinterColumns, - }; - } - } - - throw new Error(`Unknown apiVersion=${apiVersion}: Failed to find a version for CustomResourceDefinition ${this.metadata.name}`); - } - - getVersion() { - return this.getPreferedVersion().name; - } - - isNamespaced() { - return this.getScope() === "Namespaced"; - } - - getStoredVersions() { - return this.status?.storedVersions.join(", ") ?? ""; - } - - getNames() { - return this.spec.names; - } - - getConversion() { - return JSON.stringify(this.spec.conversion); - } - - getPrinterColumns(ignorePriority = true): AdditionalPrinterColumnsV1[] { - const columns = this.getPreferedVersion().additionalPrinterColumns ?? []; - - return columns - .filter(column => column.name.toLowerCase() != "age" && (ignorePriority || !column.priority)); - } - - getValidation() { - return JSON.stringify(this.getPreferedVersion().schema, null, 2); - } - - getConditions() { - if (!this.status?.conditions) return []; - - return this.status.conditions.map(condition => { - const { message, reason, lastTransitionTime, status } = condition; - - return { - ...condition, - isReady: status === "True", - tooltip: `${message || reason} (${lastTransitionTime})`, - }; - }); - } -} export class CustomResourceDefinitionApi extends KubeApi { constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions = {}) { diff --git a/packages/core/src/common/k8s-api/endpoints/daemon-set.api.injectable.ts b/packages/core/src/common/k8s-api/endpoints/daemon-set.api.injectable.ts index 8a904519b6..d9b89d6ca6 100644 --- a/packages/core/src/common/k8s-api/endpoints/daemon-set.api.injectable.ts +++ b/packages/core/src/common/k8s-api/endpoints/daemon-set.api.injectable.ts @@ -13,7 +13,7 @@ import maybeKubeApiInjectable from "../maybe-kube-api.injectable"; const daemonSetApiInjectable = getInjectable({ id: "daemon-set-api", instantiate: (di) => { - assert(di.inject(storesAndApisCanBeCreatedInjectionToken), "daemonSetApi is only available in certain environements"); + assert(di.inject(storesAndApisCanBeCreatedInjectionToken), "daemonSetApi is only available in certain environments"); return new DaemonSetApi({ logger: di.inject(loggerInjectable), diff --git a/packages/core/src/common/k8s-api/endpoints/daemon-set.api.ts b/packages/core/src/common/k8s-api/endpoints/daemon-set.api.ts index 6661e2ff43..3a9dd31b2e 100644 --- a/packages/core/src/common/k8s-api/endpoints/daemon-set.api.ts +++ b/packages/core/src/common/k8s-api/endpoints/daemon-set.api.ts @@ -4,83 +4,9 @@ */ import moment from "moment"; - -import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api"; +import type { DerivedKubeApiOptions, KubeApiDependencies, NamespacedResourceDescriptor } from "../kube-api"; import { KubeApi } from "../kube-api"; -import type { KubeObjectStatus, LabelSelector, NamespaceScopedMetadata } from "../kube-object"; -import { KubeObject } from "../kube-object"; -import type { PodTemplateSpec } from "./types/pod-template-spec"; - -export interface RollingUpdateDaemonSet { - maxUnavailable?: number | string; - maxSurge?: number | string; -} - -export interface DaemonSetUpdateStrategy { - type: string; - rollingUpdate: RollingUpdateDaemonSet; -} - -export interface DaemonSetSpec { - selector: LabelSelector; - template: PodTemplateSpec; - updateStrategy: DaemonSetUpdateStrategy; - minReadySeconds?: number; - revisionHistoryLimit?: number; -} - -export interface DaemonSetStatus extends KubeObjectStatus { - collisionCount?: number; - currentNumberScheduled: number; - desiredNumberScheduled: number; - numberAvailable?: number; - numberMisscheduled: number; - numberReady: number; - numberUnavailable?: number; - observedGeneration?: number; - updatedNumberScheduled?: number; -} - -export class DaemonSet extends KubeObject< - NamespaceScopedMetadata, - DaemonSetStatus, - DaemonSetSpec -> { - static kind = "DaemonSet"; - static namespaced = true; - static apiBase = "/apis/apps/v1/daemonsets"; - - getSelectors(): string[] { - return KubeObject.stringifyLabels(this.spec.selector.matchLabels); - } - - getNodeSelectors(): string[] { - return KubeObject.stringifyLabels(this.spec.template.spec?.nodeSelector); - } - - getTemplateLabels(): string[] { - return KubeObject.stringifyLabels(this.spec.template.metadata?.labels); - } - - getTolerations() { - return this.spec.template.spec?.tolerations ?? []; - } - - getAffinity() { - return this.spec.template.spec?.affinity; - } - - getAffinityNumber() { - return Object.keys(this.getAffinity() ?? {}).length; - } - - getImages() { - const containers = this.spec.template?.spec?.containers ?? []; - const initContainers = this.spec.template?.spec?.initContainers ?? []; - - return [...containers, ...initContainers].map(container => container.image); - } -} +import { DaemonSet } from "@k8slens/kube-object"; export class DaemonSetApi extends KubeApi { constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) { @@ -90,22 +16,15 @@ export class DaemonSetApi extends KubeApi { }); } - restart(params: { namespace: string; name: string }) { - return this.request.patch(this.getUrl(params), { - data: { - spec: { - template: { - metadata: { - annotations: { "kubectl.kubernetes.io/restartedAt" : moment.utc().format() }, - }, + restart(params: NamespacedResourceDescriptor) { + return this.patch(params, { + spec: { + template: { + metadata: { + annotations: { "kubectl.kubernetes.io/restartedAt" : moment.utc().format() }, }, }, }, - }, - { - headers: { - "content-type": "application/strategic-merge-patch+json", - }, - }); + }, "strategic"); } } diff --git a/packages/core/src/common/k8s-api/endpoints/deployment.api.ts b/packages/core/src/common/k8s-api/endpoints/deployment.api.ts index 70e7200e3c..d715612739 100644 --- a/packages/core/src/common/k8s-api/endpoints/deployment.api.ts +++ b/packages/core/src/common/k8s-api/endpoints/deployment.api.ts @@ -5,11 +5,9 @@ import moment from "moment"; -import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api"; +import type { DerivedKubeApiOptions, KubeApiDependencies, NamespacedResourceDescriptor } from "../kube-api"; import { KubeApi } from "../kube-api"; -import type { PodSpec } from "./pod.api"; -import type { KubeObjectStatus, LabelSelector, NamespaceScopedMetadata } from "../kube-object"; -import { KubeObject } from "../kube-object"; +import { Deployment } from "@k8slens/kube-object"; import { hasTypedProperty, isNumber, isObject } from "@k8slens/utilities"; export class DeploymentApi extends KubeApi { @@ -20,11 +18,11 @@ export class DeploymentApi extends KubeApi { }); } - protected getScaleApiUrl(params: { namespace: string; name: string }) { - return `${this.getUrl(params)}/scale`; + protected getScaleApiUrl(params: NamespacedResourceDescriptor) { + return `${this.formatUrlForNotListing(params)}/scale`; } - async getReplicas(params: { namespace: string; name: string }): Promise { + async getReplicas(params: NamespacedResourceDescriptor): Promise { const { status } = await this.request.get(this.getScaleApiUrl(params)); if (isObject(status) && hasTypedProperty(status, "replicas", isNumber)) { @@ -34,7 +32,7 @@ export class DeploymentApi extends KubeApi { return 0; } - scale(params: { namespace: string; name: string }, replicas: number) { + scale(params: NamespacedResourceDescriptor, replicas: number) { return this.request.patch(this.getScaleApiUrl(params), { data: { spec: { @@ -49,105 +47,15 @@ export class DeploymentApi extends KubeApi { }); } - restart(params: { namespace: string; name: string }) { - return this.request.patch(this.getUrl(params), { - data: { - spec: { - template: { - metadata: { - annotations: { "kubectl.kubernetes.io/restartedAt" : moment.utc().format() }, - }, + restart(params: NamespacedResourceDescriptor) { + return this.patch(params, { + spec: { + template: { + metadata: { + annotations: { "kubectl.kubernetes.io/restartedAt" : moment.utc().format() }, }, }, }, - }, - { - headers: { - "content-type": "application/strategic-merge-patch+json", - }, - }); - } -} - -export interface DeploymentSpec { - replicas: number; - selector: LabelSelector; - template: { - metadata: { - creationTimestamp?: string; - labels: Partial>; - annotations?: Partial>; - }; - spec: PodSpec; - }; - strategy: { - type: string; - rollingUpdate: { - maxUnavailable: number; - maxSurge: number; - }; - }; -} - -export interface DeploymentStatus extends KubeObjectStatus { - observedGeneration: number; - replicas: number; - updatedReplicas: number; - readyReplicas: number; - availableReplicas?: number; - unavailableReplicas?: number; -} - -export class Deployment extends KubeObject< - NamespaceScopedMetadata, - DeploymentStatus, - DeploymentSpec -> { - static kind = "Deployment"; - static namespaced = true; - static apiBase = "/apis/apps/v1/deployments"; - - getSelectors(): string[] { - return KubeObject.stringifyLabels(this.spec.selector.matchLabels); - } - - getNodeSelectors(): string[] { - return KubeObject.stringifyLabels(this.spec.template.spec.nodeSelector); - } - - getTemplateLabels(): string[] { - return KubeObject.stringifyLabels(this.spec.template.metadata.labels); - } - - getTolerations() { - return this.spec.template.spec.tolerations ?? []; - } - - getAffinity() { - return this.spec.template.spec.affinity; - } - - getAffinityNumber() { - return Object.keys(this.getAffinity() ?? {}).length; - } - - getConditions(activeOnly = false) { - const { conditions = [] } = this.status ?? {}; - - if (activeOnly) { - return conditions.filter(c => c.status === "True"); - } - - return conditions; - } - - getConditionsText(activeOnly = true) { - return this.getConditions(activeOnly) - .map(({ type }) => type) - .join(" "); - } - - getReplicas() { - return this.spec.replicas || 0; + }, "strategic"); } } diff --git a/packages/core/src/common/k8s-api/endpoints/endpoint.api.ts b/packages/core/src/common/k8s-api/endpoints/endpoint.api.ts index 1e045e902d..63b5e082dc 100644 --- a/packages/core/src/common/k8s-api/endpoints/endpoint.api.ts +++ b/packages/core/src/common/k8s-api/endpoints/endpoint.api.ts @@ -3,113 +3,10 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import autoBind from "auto-bind"; -import type { KubeObjectMetadata, KubeObjectScope, NamespaceScopedMetadata, ObjectReference } from "../kube-object"; -import { KubeObject } from "../kube-object"; +import type { EndpointsData } from "@k8slens/kube-object"; +import { Endpoints } from "@k8slens/kube-object"; import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api"; import { KubeApi } from "../kube-api"; -import type { KubeJsonApiData } from "../kube-json-api"; - -export function formatEndpointSubset(subset: EndpointSubset): string { - const { addresses, ports } = subset; - - if (!addresses || !ports) { - return ""; - } - - return addresses - .map(address => ( - ports - .map(port => `${address.ip}:${port.port}`) - .join(", ") - )) - .join(", "); - -} - -export interface ForZone { - name: string; -} - -export interface EndpointHints { - forZones?: ForZone[]; -} - -export interface EndpointConditions { - ready?: boolean; - serving?: boolean; - terminating?: boolean; -} - -export interface EndpointData { - addresses: string[]; - conditions?: EndpointConditions; - hints?: EndpointHints; - hostname?: string; - nodeName?: string; - targetRef?: ObjectReference; - zone?: string; -} - -export interface EndpointPort { - appProtocol?: string; - name?: string; - protocol?: string; - port: number; -} - -export interface EndpointAddress { - hostname?: string; - ip: string; - nodeName?: string; - targetRef?: ObjectReference; -} - -export interface EndpointSubset { - addresses?: EndpointAddress[]; - notReadyAddresses?: EndpointAddress[]; - ports?: EndpointPort[]; -} - -export interface EndpointsData extends KubeJsonApiData, void, void> { - subsets?: EndpointSubset[]; -} - -export class Endpoints extends KubeObject< - NamespaceScopedMetadata, - void, - void -> { - static kind = "Endpoints"; - static namespaced = true; - static apiBase = "/api/v1/endpoints"; - - subsets?: EndpointSubset[]; - - constructor({ subsets, ...rest }: EndpointsData) { - super(rest); - autoBind(this); - this.subsets = subsets; - } - - getEndpointSubsets(): Required[] { - return this.subsets?.map(({ - addresses = [], - notReadyAddresses = [], - ports = [], - }) => ({ - addresses, - notReadyAddresses, - ports, - })) ?? []; - } - - toString(): string { - return this.getEndpointSubsets() - .map(formatEndpointSubset) - .join(", ") || ""; - } -} export class EndpointsApi extends KubeApi { constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions = {}) { diff --git a/packages/core/src/common/k8s-api/endpoints/events.api.ts b/packages/core/src/common/k8s-api/endpoints/events.api.ts index 1da4b6afe8..02a5c19874 100644 --- a/packages/core/src/common/k8s-api/endpoints/events.api.ts +++ b/packages/core/src/common/k8s-api/endpoints/events.api.ts @@ -3,133 +3,10 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import moment from "moment"; -import type { KubeObjectMetadata, KubeObjectScope, ObjectReference } from "../kube-object"; -import { KubeObject } from "../kube-object"; -import { formatDuration } from "@k8slens/utilities"; +import type { KubeEventData } from "@k8slens/kube-object"; +import { KubeEvent } from "@k8slens/kube-object"; import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api"; import { KubeApi } from "../kube-api"; -import type { KubeJsonApiData } from "../kube-json-api"; - -export interface EventSeries { - count?: number; - lastObservedTime?: string; -} - -export interface EventSource { - component?: string; - host?: string; -} - -export interface KubeEventData extends KubeJsonApiData, void, void> { - action?: string; - count?: number; - eventTime?: string; - firstTimestamp?: string; - involvedObject: Required; - lastTimestamp?: string; - message?: string; - reason?: string; - related?: ObjectReference; - reportingComponent?: string; - reportingInstance?: string; - series?: EventSeries; - source?: EventSource; - type?: string; -} - -export class KubeEvent extends KubeObject, void, void> { - static kind = "Event"; - static namespaced = true; - static apiBase = "/api/v1/events"; - - action?: string; - count?: number; - eventTime?: string; - firstTimestamp?: string; - involvedObject: Required; - lastTimestamp?: string; - message?: string; - reason?: string; - related?: ObjectReference; - reportingComponent?: string; - reportingInstance?: string; - series?: EventSeries; - source?: EventSource; - - /** - * Current supported values are: - * - "Normal" - * - "Warning" - */ - type?: string; - - constructor({ - action, - count, - eventTime, - firstTimestamp, - involvedObject, - lastTimestamp, - message, - reason, - related, - reportingComponent, - reportingInstance, - series, - source, - type, - ...rest - }: KubeEventData) { - super(rest); - this.action = action; - this.count = count; - this.eventTime = eventTime; - this.firstTimestamp = firstTimestamp; - this.involvedObject = involvedObject; - this.lastTimestamp = lastTimestamp; - this.message = message; - this.reason = reason; - this.related = related; - this.reportingComponent = reportingComponent; - this.reportingInstance = reportingInstance; - this.series = series; - this.source = source; - this.type = type; - } - - isWarning() { - return this.type === "Warning"; - } - - getSource() { - if (!this.source?.component) { - return ""; - } - - const { component, host = "" } = this.source; - - return `${component} ${host}`; - } - - /** - * @deprecated This function is not reactive to changing of time. If rendering use `` instead - */ - getFirstSeenTime() { - const diff = moment().diff(this.firstTimestamp); - - return formatDuration(diff, true); - } - - /** - * @deprecated This function is not reactive to changing of time. If rendering use `` instead - */ - getLastSeenTime() { - const diff = moment().diff(this.lastTimestamp); - - return formatDuration(diff, true); - } -} export class KubeEventApi extends KubeApi { constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions = {}) { diff --git a/packages/core/src/common/k8s-api/endpoints/helm-releases.api/request-details.injectable.ts b/packages/core/src/common/k8s-api/endpoints/helm-releases.api/request-details.injectable.ts index 8c7b3dd472..8c711a019e 100644 --- a/packages/core/src/common/k8s-api/endpoints/helm-releases.api/request-details.injectable.ts +++ b/packages/core/src/common/k8s-api/endpoints/helm-releases.api/request-details.injectable.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import type { KubeJsonApiData } from "../../kube-json-api"; +import type { KubeJsonApiData } from "@k8slens/kube-object"; import { urlBuilderFor } from "@k8slens/utilities"; import apiBaseInjectable from "../../api-base.injectable"; @@ -26,7 +26,7 @@ export interface HelmReleaseDetails { export type CallForHelmReleaseDetails = (name: string, namespace: string) => Promise; -const requestDetailsEnpoint = urlBuilderFor("/v2/releases/:namespace/:name"); +const requestDetailsEndpoint = urlBuilderFor("/v2/releases/:namespace/:name"); const requestHelmReleaseDetailsInjectable = getInjectable({ id: "call-for-helm-release-details", @@ -34,7 +34,7 @@ const requestHelmReleaseDetailsInjectable = getInjectable({ instantiate: (di): CallForHelmReleaseDetails => { const apiBase = di.inject(apiBaseInjectable); - return (name, namespace) => apiBase.get(requestDetailsEnpoint.compile({ name, namespace })); + return (name, namespace) => apiBase.get(requestDetailsEndpoint.compile({ name, namespace })); }, }); diff --git a/packages/core/src/common/k8s-api/endpoints/horizontal-pod-autoscaler.api.ts b/packages/core/src/common/k8s-api/endpoints/horizontal-pod-autoscaler.api.ts index b3bdbbf21d..6f580a2880 100644 --- a/packages/core/src/common/k8s-api/endpoints/horizontal-pod-autoscaler.api.ts +++ b/packages/core/src/common/k8s-api/endpoints/horizontal-pod-autoscaler.api.ts @@ -3,365 +3,9 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import type { OptionVariant } from "@k8slens/utilities"; import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api"; import { KubeApi } from "../kube-api"; -import type { BaseKubeObjectCondition, LabelSelector, NamespaceScopedMetadata } from "../kube-object"; -import { KubeObject } from "../kube-object"; -import type { CrossVersionObjectReference } from "./types/cross-version-object-reference"; - -export enum HpaMetricType { - Resource = "Resource", - Pods = "Pods", - Object = "Object", - External = "External", - ContainerResource = "ContainerResource", -} - -export interface MetricCurrentTarget { - current?: string; - target?: string; -} - -export interface HorizontalPodAutoscalerMetricTarget { - kind: string; - name: string; - apiVersion: string; -} - -export interface V2ContainerResourceMetricSource { - container: string; - name: string; - target?: { - averageUtilization?: number; - averageValue?: string; - type?: string; - }; -} - -export interface V2Beta1ContainerResourceMetricSource { - container: string; - name: string; - targetAverageUtilization?: number; - targetAverageValue?: string; -} - -export type ContainerResourceMetricSource = - | V2ContainerResourceMetricSource - | V2Beta1ContainerResourceMetricSource; - -export interface V2ExternalMetricSource { - metricName?: string; - metricSelector?: LabelSelector; - metric?: { - name?: string; - selector?: LabelSelector; - }; - target?: { - type: string; - value?: string; - averageValue?: string; - }; -} - -export interface V2Beta1ExternalMetricSource { - metricName?: string; - metricSelector?: LabelSelector; - targetAverageValue?: string; - targetValue?: string; - metric?: { - selector?: LabelSelector; - }; -} - -export type ExternalMetricSource = - | V2Beta1ExternalMetricSource - | V2ExternalMetricSource; - -export interface V2ObjectMetricSource { - metric?: { - name?: string; - selector?: LabelSelector; - }; - target?: { - type?: string; - value?: string; - averageValue?: string; - }; - describedObject?: CrossVersionObjectReference; -} - -export interface V2Beta1ObjectMetricSource { - averageValue?: string; - metricName?: string; - selector?: LabelSelector; - targetValue?: string; - describedObject?: CrossVersionObjectReference; -} - -export type ObjectMetricSource = - | V2ObjectMetricSource - | V2Beta1ObjectMetricSource; - -export interface V2PodsMetricSource { - metric?: { - name?: string; - selector?: LabelSelector; - }; - target?: { - averageValue?: string; - type?: string; - }; -} - -export interface V2Beta1PodsMetricSource { - metricName?: string; - selector?: LabelSelector; - targetAverageValue?: string; -} - -export type PodsMetricSource = - | V2PodsMetricSource - | V2Beta1PodsMetricSource; - -export interface V2ResourceMetricSource { - name: string; - target?: { - averageUtilization?: number; - averageValue?: string; - type?: string; - }; -} - -export interface V2Beta1ResourceMetricSource { - name: string; - targetAverageUtilization?: number; - targetAverageValue?: string; -} - -export type ResourceMetricSource = - | V2ResourceMetricSource - | V2Beta1ResourceMetricSource; - -export interface BaseHorizontalPodAutoscalerMetricSpec { - containerResource: ContainerResourceMetricSource; - external: ExternalMetricSource; - object: ObjectMetricSource; - pods: PodsMetricSource; - resource: ResourceMetricSource; -} - -export type HorizontalPodAutoscalerMetricSpec = - | OptionVariant - | OptionVariant - | OptionVariant - | OptionVariant - | OptionVariant; - -interface HorizontalPodAutoscalerBehavior { - scaleUp?: HPAScalingRules; - scaleDown?: HPAScalingRules; -} - -interface HPAScalingRules { - stabilizationWindowSecond?: number; - selectPolicy?: ScalingPolicySelect; - policies?: HPAScalingPolicy[]; -} - -type ScalingPolicySelect = string; - -interface HPAScalingPolicy { - type: HPAScalingPolicyType; - value: number; - periodSeconds: number; -} - -type HPAScalingPolicyType = string; - -export interface V2ContainerResourceMetricStatus { - container?: string; - name: string; - current?: { - averageUtilization?: number; - averageValue?: string; - }; -} - -export interface V2Beta1ContainerResourceMetricStatus { - container?: string; - currentAverageUtilization?: number; - currentAverageValue?: string; - name: string; -} - -export type ContainerResourceMetricStatus = - | V2ContainerResourceMetricStatus - | V2Beta1ContainerResourceMetricStatus; - -export interface V2ExternalMetricStatus { - metric?: { - name?: string; - selector?: LabelSelector; - }; - current?: { - averageValue?: string; - value?: string; - }; -} - -export interface V2Beta1ExternalMetricStatus { - currentAverageValue?: string; - currentValue?: string; - metricName?: string; - metricSelector?: LabelSelector; -} - -export type ExternalMetricStatus = - | V2Beta1ExternalMetricStatus - | V2ExternalMetricStatus; - -export interface V2ObjectMetricStatus { - metric?: { - name?: string; - selector?: LabelSelector; - }; - current?: { - type?: string; - value?: string; - averageValue?: string; - }; - describedObject?: CrossVersionObjectReference; -} - -export interface V2Beta1ObjectMetricStatus { - averageValue?: string; - currentValue?: string; - metricName?: string; - selector?: LabelSelector; - describedObject?: CrossVersionObjectReference; -} - -export type ObjectMetricStatus = - | V2Beta1ObjectMetricStatus - | V2ObjectMetricStatus; - -export interface V2PodsMetricStatus { - selector?: LabelSelector; - metric?: { - name?: string; - selector?: LabelSelector; - }; - current?: { - averageValue?: string; - }; -} - -export interface V2Beta1PodsMetricStatus { - currentAverageValue?: string; - metricName?: string; - selector?: LabelSelector; -} - -export type PodsMetricStatus = - | V2Beta1PodsMetricStatus - | V2PodsMetricStatus; - -export interface V2ResourceMetricStatus { - name: string; - current?: { - averageUtilization?: number; - averageValue?: string; - }; -} - -export interface V2Beta1ResourceMetricStatus { - currentAverageUtilization?: number; - currentAverageValue?: string; - name: string; -} - -export type ResourceMetricStatus = - | V2Beta1ResourceMetricStatus - | V2ResourceMetricStatus; - -export interface BaseHorizontalPodAutoscalerMetricStatus { - containerResource: ContainerResourceMetricStatus; - external: ExternalMetricStatus; - object: ObjectMetricStatus; - pods: PodsMetricStatus; - resource: ResourceMetricStatus; -} - -export type HorizontalPodAutoscalerMetricStatus = - | OptionVariant - | OptionVariant - | OptionVariant - | OptionVariant - | OptionVariant; - -export interface HorizontalPodAutoscalerSpec { - scaleTargetRef: CrossVersionObjectReference; - minReplicas?: number; - maxReplicas: number; - metrics?: HorizontalPodAutoscalerMetricSpec[]; - behavior?: HorizontalPodAutoscalerBehavior; - targetCPUUtilizationPercentage?: number; // used only in autoscaling/v1 -} - -export interface HorizontalPodAutoscalerStatus { - conditions?: BaseKubeObjectCondition[]; - currentReplicas: number; - desiredReplicas: number; - currentMetrics?: HorizontalPodAutoscalerMetricStatus[]; - currentCPUUtilizationPercentage?: number; // used only in autoscaling/v1 -} - -export class HorizontalPodAutoscaler extends KubeObject< - NamespaceScopedMetadata, - HorizontalPodAutoscalerStatus, - HorizontalPodAutoscalerSpec -> { - static readonly kind = "HorizontalPodAutoscaler"; - static readonly namespaced = true; - static readonly apiBase = "/apis/autoscaling/v2/horizontalpodautoscalers"; - - getMaxPods() { - return this.spec.maxReplicas ?? 0; - } - - getMinPods() { - return this.spec.minReplicas ?? 0; - } - - getReplicas() { - return this.status?.currentReplicas ?? 0; - } - - getReadyConditions() { - return this.getConditions().filter(({ isReady }) => isReady); - } - - getConditions() { - return this.status?.conditions?.map(condition => { - const { message, reason, lastTransitionTime, status } = condition; - - return { - ...condition, - isReady: status === "True", - tooltip: `${message || reason} (${lastTransitionTime})`, - }; - }) ?? []; - } - - getMetrics() { - return this.spec.metrics ?? []; - } - - getCurrentMetrics() { - return this.status?.currentMetrics ?? []; - } -} +import { HorizontalPodAutoscaler } from "@k8slens/kube-object"; export class HorizontalPodAutoscalerApi extends KubeApi { constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) { diff --git a/packages/core/src/common/k8s-api/endpoints/index.ts b/packages/core/src/common/k8s-api/endpoints/index.ts index ad187f6565..ee2a75dfe1 100644 --- a/packages/core/src/common/k8s-api/endpoints/index.ts +++ b/packages/core/src/common/k8s-api/endpoints/index.ts @@ -46,5 +46,4 @@ export * from "./service.api"; export * from "./service-account.api"; export * from "./stateful-set.api"; export * from "./storage-class.api"; -export * from "./types"; export * from "./vertical-pod-autoscaler.api"; diff --git a/packages/core/src/common/k8s-api/endpoints/ingress-class.api.ts b/packages/core/src/common/k8s-api/endpoints/ingress-class.api.ts index 5950192179..c47b19407b 100644 --- a/packages/core/src/common/k8s-api/endpoints/ingress-class.api.ts +++ b/packages/core/src/common/k8s-api/endpoints/ingress-class.api.ts @@ -3,8 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import type { KubeObjectMetadata, KubeObjectScope } from "../kube-object"; -import { KubeObject } from "../kube-object"; +import { IngressClass } from "@k8slens/kube-object"; import type { KubeApiDependencies, ResourceDescriptor } from "../kube-api"; import { KubeApi } from "../kube-api"; @@ -18,84 +17,12 @@ export class IngressClassApi extends KubeApi { } setAsDefault({ name }: ResourceDescriptor, isDefault = true) { - const reqUrl = this.formatUrlForNotListing({ name }); - - return this.request.patch(reqUrl, { - data: { - metadata: { - annotations: { - [IngressClass.ANNOTATION_IS_DEFAULT]: JSON.stringify(isDefault), - }, + return this.patch({ name }, { + metadata: { + annotations: { + [IngressClass.ANNOTATION_IS_DEFAULT]: String(isDefault), }, }, - }, { - headers: { - "content-type": "application/strategic-merge-patch+json", - }, - }); - } -} - -// API docs: https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#ingressclass-v1-networking-k8s-io -export type IngressClassMetadata = KubeObjectMetadata & { - "name": string; - "labels"?: { - [name: string]: string | undefined; - "app.kubernetes.io/component"?: "controller"; - }; - "annotations"?: { - [name: string]: string | undefined; - "ingressclass.kubernetes.io/is-default-class"?: "true"; - }; -}; - -export interface IngressClassParametersReference { - "apiGroup": string; // k8s.example.net - "scope": "Namespace" | "Cluster"; - "kind": "ClusterIngressParameter" | "IngressParameter"; - "name": string; // external-config-1 - "namespace"?: string; // namespaced for IngressClass must be defined in `spec.parameters.namespace` instead of `metadata.namespace` (!) -} - -export interface IngressClassSpec { - controller: string; // "example.com/ingress-controller" - parameters?: IngressClassParametersReference; -} - -export interface IngressClassStatus { -} - -export class IngressClass extends KubeObject { - static readonly kind = "IngressClass"; - static readonly namespaced = false; - static readonly apiBase = "/apis/networking.k8s.io/v1/ingressclasses"; - static readonly ANNOTATION_IS_DEFAULT = "ingressclass.kubernetes.io/is-default-class"; - - getController(): string { - return this.spec.controller; - } - - getCtrlApiGroup() { - return this.spec?.parameters?.apiGroup; - } - - getCtrlScope() { - return this.spec?.parameters?.scope; - } - - getCtrlNs() { - return this.spec?.parameters?.namespace; - } - - getCtrlKind() { - return this.spec?.parameters?.kind; - } - - getCtrlName() { - return this.spec?.parameters?.name as string; - } - - get isDefault() { - return this.metadata.annotations?.[IngressClass.ANNOTATION_IS_DEFAULT] === "true"; + }, "strategic"); } } diff --git a/packages/core/src/common/k8s-api/endpoints/ingress.api.ts b/packages/core/src/common/k8s-api/endpoints/ingress.api.ts index 2a9a49ad8e..ca8c018935 100644 --- a/packages/core/src/common/k8s-api/endpoints/ingress.api.ts +++ b/packages/core/src/common/k8s-api/endpoints/ingress.api.ts @@ -3,12 +3,9 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import type { NamespaceScopedMetadata, TypedLocalObjectReference } from "../kube-object"; -import { KubeObject } from "../kube-object"; -import { hasTypedProperty, isString, iter } from "@k8slens/utilities"; +import { Ingress } from "@k8slens/kube-object"; import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api"; import { KubeApi } from "../kube-api"; -import type { RequireExactlyOne } from "type-fest"; export class IngressApi extends KubeApi { constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) { @@ -21,193 +18,3 @@ export class IngressApi extends KubeApi { }); } } - -export interface ILoadBalancerIngress { - hostname?: string; - ip?: string; -} - -// extensions/v1beta1 -export interface ExtensionsBackend { - serviceName?: string; - servicePort?: number | string; -} - -// networking.k8s.io/v1 -export interface NetworkingBackend { - service?: IngressService; -} - -export type IngressBackend = (ExtensionsBackend | NetworkingBackend) & { - resource?: TypedLocalObjectReference; -}; - -export interface IngressService { - name: string; - port: RequireExactlyOne<{ - name: string; - number: number; - }>; -} - -function isExtensionsBackend(backend: IngressBackend): backend is ExtensionsBackend { - return hasTypedProperty(backend, "serviceName", isString); -} - -/** - * Format an ingress backend into the name of the service and port - * @param backend The ingress target - */ -export function getBackendServiceNamePort(backend: IngressBackend | undefined): string { - if (!backend) { - return ""; - } - - if (isExtensionsBackend(backend)) { - return `${backend.serviceName}:${backend.servicePort}`; - } - - if (backend.service) { - const { name, port } = backend.service; - - return `${name}:${port.number ?? port.name}`; - } - - return ""; -} - -export interface HTTPIngressPath { - pathType: "Exact" | "Prefix" | "ImplementationSpecific"; - path?: string; - backend?: IngressBackend; -} - -export interface HTTPIngressRuleValue { - paths: HTTPIngressPath[]; -} - -export interface IngressRule { - host?: string; - http?: HTTPIngressRuleValue; -} - -export interface IngressSpec { - tls: { - secretName: string; - }[]; - rules?: IngressRule[]; - // extensions/v1beta1 - backend?: ExtensionsBackend; - /** - * The default backend which is exactly on of: - * - service - * - resource - */ - defaultBackend?: RequireExactlyOne; -} - -export interface IngressStatus { - loadBalancer: { - ingress?: ILoadBalancerIngress[]; - }; -} - -export class Ingress extends KubeObject< - NamespaceScopedMetadata, - IngressStatus, - IngressSpec -> { - static readonly kind = "Ingress"; - static readonly namespaced = true; - static readonly apiBase = "/apis/networking.k8s.io/v1/ingresses"; - - getRules() { - return this.spec.rules ?? []; - } - - getRoutes(): string[] { - return computeRouteDeclarations(this).map(({ url, service }) => `${url} ⇢ ${service}`); - } - - getServiceNamePort(): ExtensionsBackend | undefined { - const { spec: { backend, defaultBackend } = {}} = this; - - const serviceName = defaultBackend?.service?.name ?? backend?.serviceName; - const servicePort = defaultBackend?.service?.port.number ?? defaultBackend?.service?.port.name ?? backend?.servicePort; - - if (!serviceName || !servicePort) { - return undefined; - } - - return { - serviceName, - servicePort, - }; - } - - getHosts() { - const { spec: { rules = [] }} = this; - - return [...iter.filterMap(rules, rule => rule.host)]; - } - - getPorts() { - const ports: number[] = []; - const { spec: { tls, rules = [], backend, defaultBackend }} = this; - const httpPort = 80; - const tlsPort = 443; - // Note: not using the port name (string) - const servicePort = defaultBackend?.service?.port.number ?? backend?.servicePort; - - if (rules.length > 0) { - if (rules.some(rule => rule.http)) { - ports.push(httpPort); - } - } else if (servicePort !== undefined) { - ports.push(Number(servicePort)); - } - - if (tls && tls.length > 0) { - ports.push(tlsPort); - } - - return ports.join(", "); - } - - getLoadBalancers() { - return this.status?.loadBalancer?.ingress?.map(address => ( - address.hostname || address.ip - )) ?? []; - } -} - -export interface ComputedIngressRoute { - displayAsLink: boolean; - pathname: string; - url: string; - service: string; -} - -export function computeRuleDeclarations(ingress: Ingress, rule: IngressRule): ComputedIngressRoute[] { - const { host = "*", http: { paths } = { paths: [] }} = rule; - const protocol = (ingress.spec?.tls?.length ?? 0) === 0 - ? "http" - : "https"; - - return paths.map(({ path = "/", backend }) => ({ - displayAsLink: !host.includes("*"), - pathname: path, - url: `${protocol}://${host}${path}`, - service: getBackendServiceNamePort(backend), - })); -} - -export function computeRouteDeclarations(ingress: Ingress): ComputedIngressRoute[] { - return ingress.getRules().flatMap(rule => computeRuleDeclarations(ingress, rule)); -} diff --git a/packages/core/src/common/k8s-api/endpoints/job.api.ts b/packages/core/src/common/k8s-api/endpoints/job.api.ts index ef47419e80..15d145cbba 100644 --- a/packages/core/src/common/k8s-api/endpoints/job.api.ts +++ b/packages/core/src/common/k8s-api/endpoints/job.api.ts @@ -5,94 +5,7 @@ import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api"; import { KubeApi } from "../kube-api"; -import type { PodSpec } from "./pod.api"; -import type { Container } from "./types/container"; -import type { KubeObjectStatus, LabelSelector, NamespaceScopedMetadata } from "../kube-object"; -import { KubeObject } from "../kube-object"; - -export interface JobSpec { - parallelism?: number; - completions?: number; - backoffLimit?: number; - selector?: LabelSelector; - template: { - metadata: { - creationTimestamp?: string; - labels?: Partial>; - annotations?: Partial>; - }; - spec: PodSpec; - }; - containers?: Container[]; - restartPolicy?: string; - terminationGracePeriodSeconds?: number; - dnsPolicy?: string; - serviceAccountName?: string; - serviceAccount?: string; - schedulerName?: string; -} - -export interface JobStatus extends KubeObjectStatus { - startTime: string; - completionTime: string; - succeeded: number; -} - -export class Job extends KubeObject< - NamespaceScopedMetadata, - JobStatus, - JobSpec -> { - static readonly kind = "Job"; - static readonly namespaced = true; - static readonly apiBase = "/apis/batch/v1/jobs"; - - getSelectors(): string[] { - return KubeObject.stringifyLabels(this.spec.selector?.matchLabels); - } - - getNodeSelectors(): string[] { - return KubeObject.stringifyLabels(this.spec.template.spec.nodeSelector); - } - - getTemplateLabels(): string[] { - return KubeObject.stringifyLabels(this.spec.template.metadata.labels); - } - - getTolerations() { - return this.spec.template.spec.tolerations ?? []; - } - - getAffinity() { - return this.spec.template.spec.affinity; - } - - getAffinityNumber() { - return Object.keys(this.getAffinity() ?? {}).length; - } - - getDesiredCompletions() { - return this.spec.completions ?? 0; - } - - getCompletions() { - return this.status?.succeeded ?? 0; - } - - getParallelism() { - return this.spec.parallelism; - } - - getCondition() { - // Type of Job condition could be only Complete or Failed - // https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.21/#jobcondition-v1-batch - return this.status?.conditions?.find(({ status }) => status === "True"); - } - - getImages() { - return this.spec.template.spec.containers?.map(container => container.image) ?? []; - } -} +import { Job } from "@k8slens/kube-object"; export class JobApi extends KubeApi { constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) { diff --git a/packages/core/src/common/k8s-api/endpoints/lease.api.ts b/packages/core/src/common/k8s-api/endpoints/lease.api.ts index 25f636dcfb..66a3adb960 100644 --- a/packages/core/src/common/k8s-api/endpoints/lease.api.ts +++ b/packages/core/src/common/k8s-api/endpoints/lease.api.ts @@ -5,46 +5,7 @@ import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api"; import { KubeApi } from "../kube-api"; -import type { NamespaceScopedMetadata } from "../kube-object"; -import { KubeObject } from "../kube-object"; - -export interface LeaseSpec { - acquireTime?: string; - holderIdentity: string; - leaseDurationSeconds: number; - leaseTransitions?: number; - renewTime: string; -} - -export class Lease extends KubeObject< - NamespaceScopedMetadata, - void, - LeaseSpec -> { - static readonly kind = "Lease"; - static readonly namespaced = true; - static readonly apiBase = "/apis/coordination.k8s.io/v1/leases"; - - getAcquireTime(): string { - return this.spec.acquireTime || ""; - } - - getHolderIdentity(): string { - return this.spec.holderIdentity; - } - - getLeaseDurationSeconds(): number { - return this.spec.leaseDurationSeconds; - } - - getLeaseTransitions(): number | undefined { - return this.spec.leaseTransitions; - } - - getRenewTime(): string { - return this.spec.renewTime; - } -} +import { Lease } from "@k8slens/kube-object"; export class LeaseApi extends KubeApi { constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) { diff --git a/packages/core/src/common/k8s-api/endpoints/limit-range.api.ts b/packages/core/src/common/k8s-api/endpoints/limit-range.api.ts index eec533993c..a393d4ab06 100644 --- a/packages/core/src/common/k8s-api/endpoints/limit-range.api.ts +++ b/packages/core/src/common/k8s-api/endpoints/limit-range.api.ts @@ -3,63 +3,10 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import type { NamespaceScopedMetadata } from "../kube-object"; -import { KubeObject } from "../kube-object"; +import { LimitRange } from "@k8slens/kube-object"; import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api"; import { KubeApi } from "../kube-api"; -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; -} - -export interface LimitRangeSpec { - limits: LimitRangeItem[]; -} - -export class LimitRange extends KubeObject< - NamespaceScopedMetadata, - void, - LimitRangeSpec -> { - static readonly kind = "LimitRange"; - static readonly namespaced = true; - static readonly apiBase = "/api/v1/limitranges"; - - 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 class LimitRangeApi extends KubeApi { constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) { diff --git a/packages/core/src/common/k8s-api/endpoints/metrics.api/request-metrics.injectable.ts b/packages/core/src/common/k8s-api/endpoints/metrics.api/request-metrics.injectable.ts index 08ed4c211a..003c5ca692 100644 --- a/packages/core/src/common/k8s-api/endpoints/metrics.api/request-metrics.injectable.ts +++ b/packages/core/src/common/k8s-api/endpoints/metrics.api/request-metrics.injectable.ts @@ -7,7 +7,6 @@ import { getSecondsFromUnixEpoch } from "../../../utils/date/get-current-date-ti import apiBaseInjectable from "../../api-base.injectable"; import type { MetricData } from "../metrics.api"; - export interface RequestMetricsParams { /** * timestamp in seconds or valid date-string diff --git a/packages/core/src/common/k8s-api/endpoints/metrics.api/request-persistent-volume-claim-metrics.injectable.ts b/packages/core/src/common/k8s-api/endpoints/metrics.api/request-persistent-volume-claim-metrics.injectable.ts index 5cafb5ade0..3291e02922 100644 --- a/packages/core/src/common/k8s-api/endpoints/metrics.api/request-persistent-volume-claim-metrics.injectable.ts +++ b/packages/core/src/common/k8s-api/endpoints/metrics.api/request-persistent-volume-claim-metrics.injectable.ts @@ -4,7 +4,7 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import type { MetricData } from "../metrics.api"; -import type { PersistentVolumeClaim } from "../persistent-volume-claim.api"; +import type { PersistentVolumeClaim } from "@k8slens/kube-object"; import requestMetricsInjectable from "./request-metrics.injectable"; export interface PersistentVolumeClaimMetricData { diff --git a/packages/core/src/common/k8s-api/endpoints/metrics.api/request-pod-metrics-for-daemon-sets.injectable.ts b/packages/core/src/common/k8s-api/endpoints/metrics.api/request-pod-metrics-for-daemon-sets.injectable.ts index d5653574ff..dcd41cbdb8 100644 --- a/packages/core/src/common/k8s-api/endpoints/metrics.api/request-pod-metrics-for-daemon-sets.injectable.ts +++ b/packages/core/src/common/k8s-api/endpoints/metrics.api/request-pod-metrics-for-daemon-sets.injectable.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import type { DaemonSet } from "../daemon-set.api"; +import type { DaemonSet } from "@k8slens/kube-object"; import type { MetricData } from "../metrics.api"; import requestMetricsInjectable from "./request-metrics.injectable"; diff --git a/packages/core/src/common/k8s-api/endpoints/metrics.api/request-pod-metrics-for-deployments.injectable.ts b/packages/core/src/common/k8s-api/endpoints/metrics.api/request-pod-metrics-for-deployments.injectable.ts index ced8ccd7fe..d44b97d5db 100644 --- a/packages/core/src/common/k8s-api/endpoints/metrics.api/request-pod-metrics-for-deployments.injectable.ts +++ b/packages/core/src/common/k8s-api/endpoints/metrics.api/request-pod-metrics-for-deployments.injectable.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import type { Deployment } from "../deployment.api"; +import type { Deployment } from "@k8slens/kube-object"; import type { MetricData } from "../metrics.api"; import requestMetricsInjectable from "./request-metrics.injectable"; diff --git a/packages/core/src/common/k8s-api/endpoints/metrics.api/request-pod-metrics-for-jobs.injectable.ts b/packages/core/src/common/k8s-api/endpoints/metrics.api/request-pod-metrics-for-jobs.injectable.ts index d52f028cf0..a0bff54465 100644 --- a/packages/core/src/common/k8s-api/endpoints/metrics.api/request-pod-metrics-for-jobs.injectable.ts +++ b/packages/core/src/common/k8s-api/endpoints/metrics.api/request-pod-metrics-for-jobs.injectable.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import type { Job } from "../job.api"; +import type { Job } from "@k8slens/kube-object"; import type { MetricData } from "../metrics.api"; import requestMetricsInjectable from "./request-metrics.injectable"; diff --git a/packages/core/src/common/k8s-api/endpoints/metrics.api/request-pod-metrics-for-replica-sets.injectable.ts b/packages/core/src/common/k8s-api/endpoints/metrics.api/request-pod-metrics-for-replica-sets.injectable.ts index 4186cba7b4..f3d6fad3a5 100644 --- a/packages/core/src/common/k8s-api/endpoints/metrics.api/request-pod-metrics-for-replica-sets.injectable.ts +++ b/packages/core/src/common/k8s-api/endpoints/metrics.api/request-pod-metrics-for-replica-sets.injectable.ts @@ -4,7 +4,7 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import type { MetricData } from "../metrics.api"; -import type { ReplicaSet } from "../replica-set.api"; +import type { ReplicaSet } from "@k8slens/kube-object"; import requestMetricsInjectable from "./request-metrics.injectable"; export interface ReplicaSetPodMetricData { diff --git a/packages/core/src/common/k8s-api/endpoints/metrics.api/request-pod-metrics-for-stateful-sets.injectable.ts b/packages/core/src/common/k8s-api/endpoints/metrics.api/request-pod-metrics-for-stateful-sets.injectable.ts index 227961bc02..15971743dc 100644 --- a/packages/core/src/common/k8s-api/endpoints/metrics.api/request-pod-metrics-for-stateful-sets.injectable.ts +++ b/packages/core/src/common/k8s-api/endpoints/metrics.api/request-pod-metrics-for-stateful-sets.injectable.ts @@ -4,7 +4,7 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import type { MetricData } from "../metrics.api"; -import type { StatefulSet } from "../stateful-set.api"; +import type { StatefulSet } from "@k8slens/kube-object"; import requestMetricsInjectable from "./request-metrics.injectable"; export interface StatefulSetPodMetricData { diff --git a/packages/core/src/common/k8s-api/endpoints/metrics.api/request-pod-metrics.injectable.ts b/packages/core/src/common/k8s-api/endpoints/metrics.api/request-pod-metrics.injectable.ts index e14e5dc293..d2de4f4d40 100644 --- a/packages/core/src/common/k8s-api/endpoints/metrics.api/request-pod-metrics.injectable.ts +++ b/packages/core/src/common/k8s-api/endpoints/metrics.api/request-pod-metrics.injectable.ts @@ -4,7 +4,7 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import type { MetricData } from "../metrics.api"; -import type { Pod } from "../pod.api"; +import type { Pod } from "@k8slens/kube-object"; import requestMetricsInjectable from "./request-metrics.injectable"; export interface PodMetricData { diff --git a/packages/core/src/common/k8s-api/endpoints/mutating-webhook-configuration.api.ts b/packages/core/src/common/k8s-api/endpoints/mutating-webhook-configuration.api.ts index 241a5c09cb..ac1bd51a2a 100644 --- a/packages/core/src/common/k8s-api/endpoints/mutating-webhook-configuration.api.ts +++ b/packages/core/src/common/k8s-api/endpoints/mutating-webhook-configuration.api.ts @@ -2,151 +2,9 @@ * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ -import type { LabelSelector, NamespaceScopedMetadata, KubeObjectMetadata, KubeObjectScope } from "../kube-object"; -import { KubeObject } from "../kube-object"; +import { MutatingWebhookConfiguration } from "@k8slens/kube-object"; import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api"; import { KubeApi } from "../kube-api"; -import type { KubeJsonApiData } from "../kube-json-api"; - -interface WebhookClientConfig { - // `url` gives the location of the webhook - url?: string; - - // `service` is a reference to the service for this webhook. Either `service` or `url` must be specified. - service?: ServiceReference; - - // `caBundle` is a PEM encoded CA bundle which will be used to validate the webhook's server certificate. - // If unspecified, system trust roots on the apiserver are used. - caBundle?: string; -} - -interface RuleWithOperations { - // APIGroups is the API groups the resources belong to. '*' is all groups. If '*' is present, the length of the slice must be one. - apiGroups: string[]; - - // APIVersions is the API versions the resources belong to. '*' is all versions. - apiVersions?: string[]; - - // Resources is a list of resources this rule applies to. - // For example: 'pods' means pods. - // '*' means all resources, but not subresources. - // 'pods/' means all subresources of pods. - // '*/scale' means all scale subresources. Allowed values are "Resource" / "Resource/Scale" / "Resource/Status". - resources: string[]; - - // Operations is a list of operations this rule applies to. - // The valid values are: "CREATE" / "UPDATE" / "DELETE" / "CONNECT". - operations: string[]; - - // Scope specifies the scope of this rule. Valid values are "Cluster" / "Namespace". - // Default is "Cluster". - scope?: string; -} - -export interface Webhook { - // The name of the webhook configuration. - name: string; - - // ClientConfig defines how to communicate with the hook. - clientConfig: WebhookClientConfig; - - // Rules describes what operations on what resources/subresources the webhook cares about. - // The webhook cares about an operation if it matches _any_ Rule. - rules?: RuleWithOperations[]; - - // AdmissionReviewVersions is an ordered list of preferred `AdmissionReview` versions - // the webhook expects. API server will try to use first version in the list which it - // supports. If none of the versions specified in this list supported by API server, - // validation will fail for this object. - admissionReviewVersions?: string[]; - - // TimeoutSeconds specifies the timeout for this webhook. After the timeout passes, the webhook call will be ignored - // or the API call will fail depending on the failure policy. - timeoutSeconds?: number; - - // FailurePolicy specifies how unrecognized errors from the webhook are handled - allowed values are Ignore or Fail. - // Defaults to Fail. - failurePolicy?: string; - - // matchPolicy defines how the "rules" list is used to match incoming requests. Allowed values are "Exact" or "Equivalent". - // - Exact: match a request only if it exactly matches a specified rule. - // - Equivalent: match a request if modifies a resource listed in rules, even via another API group or version. - // Defaults to "Equivalent". - matchPolicy?: string; - - // NamespaceSelector decides whether to run the webhook on an object based on whether the namespace for that object - // matches the selector. If the object itself is a namespace, the matching is performed on object.metadata.labels. - // If both the object and the webhook configuration specify namespaceSelector, they must match. - namespaceSelector?: LabelSelector; - - // ObjectSelector decides whether to run the webhook based on if the object has matching labels. - // objectSelector and namespaceSelector are ANDed. An empty objectSelector matches all objects. - // A null objectSelector matches no objects. - objectSelector?: LabelSelector; - - // SideEffects states whether this webhookk should run when no mutating or validating webhook - // needs to run. This should be false when the webhook only applies to resources that have - // the sideEffects field set to None. Defaults to true. - sideEffects?: string; - - // reinvocationPolicy indicates whether this webhook should be called multiple times as part of a - // single admission evaluation. Allowed values are "Never" and "IfNeeded" - reinvocationPolicy?: "Never" | "IfNeeded"; -} - -export interface MutatingWebhook extends Webhook { -} - -interface ServiceReference { - // `namespace` is the namespace of the service. - namespace: string; - - // `name` is the name of the service. - name: string; - - // `path` is an optional URL path which will be sent in any request to this service. - path?: string; - - // `port` is an optional service port which will be used when accessing the service. - port?: number | string; -} - -interface MutatingWebhookConfigurationData extends KubeJsonApiData, void, void> { - webhooks?: MutatingWebhook[]; -} - -export class MutatingWebhookConfiguration extends KubeObject< - NamespaceScopedMetadata, - void, - void -> { - static kind = "MutatingWebhookConfiguration"; - static namespaced = false; - static apiBase = "/apis/admissionregistration.k8s.io/v1/mutatingwebhookconfigurations"; - - webhooks?: MutatingWebhook[]; - - constructor({ webhooks, ...rest }: MutatingWebhookConfigurationData) { - super(rest); - this.webhooks = webhooks; - } - - getWebhooks(): MutatingWebhook[] { - return this.webhooks ?? []; - } - - getClientConfig(serviceName: string, serviceNamespace: string): WebhookClientConfig | undefined { - const webhooks = this.getWebhooks(); - - for (const webhook of webhooks) { - if (webhook.clientConfig.service?.name === serviceName && webhook.clientConfig.service?.namespace === serviceNamespace) { - return webhook.clientConfig; - } - } - - return undefined; - } -} export class MutatingWebhookConfigurationApi extends KubeApi { constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) { diff --git a/packages/core/src/common/k8s-api/endpoints/namespace.api.ts b/packages/core/src/common/k8s-api/endpoints/namespace.api.ts index 5d4c925126..65050a544d 100644 --- a/packages/core/src/common/k8s-api/endpoints/namespace.api.ts +++ b/packages/core/src/common/k8s-api/endpoints/namespace.api.ts @@ -5,47 +5,7 @@ import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api"; import { KubeApi } from "../kube-api"; -import type { ClusterScopedMetadata, KubeObjectStatus } from "../kube-object"; -import { KubeObject } from "../kube-object"; - -export enum NamespaceStatusKind { - ACTIVE = "Active", - TERMINATING = "Terminating", -} - -export interface NamespaceSpec { - finalizers?: string[]; -} - -export interface NamespaceStatus extends KubeObjectStatus { - phase?: string; -} - -export class Namespace extends KubeObject< - ClusterScopedMetadata, - NamespaceStatus, - NamespaceSpec -> { - static readonly kind = "Namespace"; - static readonly namespaced = false; - static readonly apiBase = "/api/v1/namespaces"; - - getStatus() { - return this.status?.phase ?? "-"; - } - - isSubnamespace() { - return this.getAnnotations().find(annotation => annotation.includes("hnc.x-k8s.io/subnamespace-of")); - } - - isChildOf(parentName: string) { - return this.getLabels().find(label => label === `${parentName}.tree.hnc.x-k8s.io/depth=1`); - } - - isControlledByHNC() { - return this.getLabels().includes("hnc.x-k8s.io/included-namespace=true"); - } -} +import { Namespace } from "@k8slens/kube-object"; export class NamespaceApi extends KubeApi { constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) { diff --git a/packages/core/src/common/k8s-api/endpoints/network-policy.api.ts b/packages/core/src/common/k8s-api/endpoints/network-policy.api.ts index 78520d5539..bf0e9efd25 100644 --- a/packages/core/src/common/k8s-api/endpoints/network-policy.api.ts +++ b/packages/core/src/common/k8s-api/endpoints/network-policy.api.ts @@ -3,126 +3,10 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import type { LabelSelector, NamespaceScopedMetadata } from "../kube-object"; -import { KubeObject } from "../kube-object"; +import { NetworkPolicy } from "@k8slens/kube-object"; import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api"; import { KubeApi } from "../kube-api"; -export interface IPolicyIpBlock { - cidr: string; - except?: string[]; -} - -/** - * @deprecated Use `LabelSelector` instead - */ -export type IPolicySelector = LabelSelector; - -export interface NetworkPolicyPort { - /** - * The protocol which network traffic must match. - * - * One of: - * - `"TCP"` - * - `"UDP"` - * - `"SCTP"` - * - * @default "TCP" - */ - protocol?: string; - - /** - * The port on the given protocol. This can either be a numerical or named - * port on a pod. If this field is not provided, this matches all port names and - * numbers. - * - * If present, only traffic on the specified protocol AND port will be matched. - */ - port?: number | string; - - /** - * If set, indicates that the range of ports from port to endPort, inclusive, - * should be allowed by the policy. This field cannot be defined if the port field - * is not defined or if the port field is defined as a named (string) port. - * - * The endPort must be equal or greater than port. - */ - endPort?: number; -} - -export interface NetworkPolicyPeer { - /** - * IPBlock defines policy on a particular IPBlock. If this field is set then - * neither of the other fields can be. - */ - ipBlock?: IPolicyIpBlock; - - /** - * Selects Namespaces using cluster-scoped labels. This field follows standard label - * selector semantics; if present but empty, it selects all namespaces. - * - * If PodSelector is also set, then the NetworkPolicyPeer as a whole selects - * the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. - * - * Otherwise it selects all Pods in the Namespaces selected by NamespaceSelector. - */ - namespaceSelector?: LabelSelector; - - /** - * This is a label selector which selects Pods. This field follows standard label - * selector semantics; if present but empty, it selects all pods. - * - * If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects - * the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. - * - * Otherwise it selects the Pods matching PodSelector in the policy's own Namespace. - */ - podSelector?: LabelSelector; -} - -export interface IPolicyIngress { - from?: NetworkPolicyPeer[]; - ports?: NetworkPolicyPort[]; -} - -export interface IPolicyEgress { - to?: NetworkPolicyPeer[]; - ports?: NetworkPolicyPort[]; -} - -export type PolicyType = "Ingress" | "Egress"; - -export interface NetworkPolicySpec { - podSelector: LabelSelector; - policyTypes?: PolicyType[]; - ingress?: IPolicyIngress[]; - egress?: IPolicyEgress[]; -} - -export class NetworkPolicy extends KubeObject< - NamespaceScopedMetadata, - void, - NetworkPolicySpec -> { - static readonly kind = "NetworkPolicy"; - static readonly namespaced = true; - static readonly apiBase = "/apis/networking.k8s.io/v1/networkpolicies"; - - getMatchLabels(): string[] { - if (!this.spec.podSelector || !this.spec.podSelector.matchLabels) return []; - - return Object - .entries(this.spec.podSelector.matchLabels) - .map(data => data.join(":")); - } - - getTypes(): string[] { - if (!this.spec.policyTypes) return []; - - return this.spec.policyTypes; - } -} - export class NetworkPolicyApi extends KubeApi { constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions = {}) { super(deps, { diff --git a/packages/core/src/common/k8s-api/endpoints/node.api.ts b/packages/core/src/common/k8s-api/endpoints/node.api.ts index 31839b24f8..5173ed719b 100644 --- a/packages/core/src/common/k8s-api/endpoints/node.api.ts +++ b/packages/core/src/common/k8s-api/endpoints/node.api.ts @@ -3,12 +3,9 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import type { BaseKubeObjectCondition, ClusterScopedMetadata } from "../kube-object"; -import { KubeObject } from "../kube-object"; -import { cpuUnitsToNumber, unitsToBytes, isObject } from "@k8slens/utilities"; +import { Node } from "@k8slens/kube-object"; import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api"; import { KubeApi } from "../kube-api"; -import { TypedRegEx } from "typed-regex"; export class NodeApi extends KubeApi { constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) { @@ -18,246 +15,3 @@ export class NodeApi extends KubeApi { }); } } - -export interface NodeTaint { - key: string; - value?: string; - effect: string; - timeAdded: string; -} - -export function formatNodeTaint(taint: NodeTaint): string { - if (taint.value) { - return `${taint.key}=${taint.value}:${taint.effect}`; - } - - return `${taint.key}:${taint.effect}`; -} - -export interface NodeCondition extends BaseKubeObjectCondition { - /** - * Last time we got an update on a given condition. - */ - lastHeartbeatTime?: string; -} - -/** - * These role label prefixs are the ones that are for master nodes - * - * The `master` label has been deprecated in Kubernetes 1.20, and will be removed in 1.25 so we - * have to also use the newer `control-plane` label - */ -const masterNodeLabels = [ - "master", - "control-plane", -]; - -/** - * This regex is used in the `getRoleLabels()` method bellow, but placed here - * as factoring out regexes is best practice. - */ -const nodeRoleLabelKeyMatcher = TypedRegEx("^.*node-role.kubernetes.io/+(?.+)$"); - -export interface NodeSpec { - podCIDR?: string; - podCIDRs?: string[]; - providerID?: string; - /** - * @deprecated see https://issues.k8s.io/61966 - */ - externalID?: string; - taints?: NodeTaint[]; - unschedulable?: boolean; -} - -export interface NodeAddress { - type: "Hostname" | "ExternalIP" | "InternalIP"; - address: string; -} - -export interface NodeStatusResources extends Partial> { - cpu?: string; - "ephemeral-storage"?: string; - "hugepages-1Gi"?: string; - "hugepages-2Mi"?: string; - memory?: string; - pods?: string; -} - -export interface ConfigMapNodeConfigSource { - kubeletConfigKey: string; - name: string; - namespace: string; - resourceVersion?: string; - uid?: string; -} - -export interface NodeConfigSource { - configMap?: ConfigMapNodeConfigSource; -} - -export interface NodeConfigStatus { - active?: NodeConfigSource; - assigned?: NodeConfigSource; - lastKnownGood?: NodeConfigSource; - error?: string; -} - -export interface DaemonEndpoint { - Port: number; //it must be uppercase for backwards compatibility -} - -export interface NodeDaemonEndpoints { - kubeletEndpoint?: DaemonEndpoint; -} - -export interface ContainerImage { - names?: string[]; - sizeBytes?: number; -} - -export interface NodeSystemInfo { - architecture: string; - bootID: string; - containerRuntimeVersion: string; - kernelVersion: string; - kubeProxyVersion: string; - kubeletVersion: string; - machineID: string; - operatingSystem: string; - osImage: string; - systemUUID: string; -} - -export interface AttachedVolume { - name: string; - devicePath: string; -} - -export interface NodeStatus { - capacity?: NodeStatusResources; - allocatable?: NodeStatusResources; - conditions?: NodeCondition[]; - addresses?: NodeAddress[]; - config?: NodeConfigStatus; - daemonEndpoints?: NodeDaemonEndpoints; - images?: ContainerImage[]; - nodeInfo?: NodeSystemInfo; - phase?: string; - volumesInUse?: string[]; - volumesAttached?: AttachedVolume[]; -} - -export class Node extends KubeObject< - ClusterScopedMetadata, - NodeStatus, - NodeSpec -> { - static readonly kind = "Node"; - static readonly namespaced = false; - static readonly apiBase = "/api/v1/nodes"; - - /** - * Returns the concatination of all current condition types which have a status - * of `"True"` - */ - getNodeConditionText(): string { - if (!this.status?.conditions) { - return ""; - } - - return this.status.conditions - .filter(condition => condition.status === "True") - .map(condition => condition.type) - .join(" "); - } - - getTaints() { - return this.spec.taints || []; - } - - isMasterNode(): boolean { - return this.getRoleLabelItems() - .some(roleLabel => masterNodeLabels.includes(roleLabel)); - } - - getRoleLabelItems(): string[] { - const { labels } = this.metadata; - const roleLabels: string[] = []; - - if (!isObject(labels)) { - return roleLabels; - } - - for (const labelKey of Object.keys(labels)) { - const match = nodeRoleLabelKeyMatcher.match(labelKey); - - if (match?.groups) { - roleLabels.push(match.groups.role); - } - } - - if (typeof labels["kubernetes.io/role"] === "string") { - roleLabels.push(labels["kubernetes.io/role"]); - } - - if (typeof labels["node.kubernetes.io/role"] === "string") { - roleLabels.push(labels["node.kubernetes.io/role"]); - } - - return roleLabels; - } - - getRoleLabels(): string { - return this.getRoleLabelItems().join(", "); - } - - getCpuCapacity() { - if (!this.status?.capacity || !this.status.capacity.cpu) return 0; - - return cpuUnitsToNumber(this.status.capacity.cpu); - } - - getMemoryCapacity() { - if (!this.status?.capacity || !this.status.capacity.memory) return 0; - - return unitsToBytes(this.status.capacity.memory); - } - - getConditions(): NodeCondition[] { - const conditions = this.status?.conditions || []; - - if (this.isUnschedulable()) { - return [{ type: "SchedulingDisabled", status: "True" }, ...conditions]; - } - - return conditions; - } - - getActiveConditions() { - return this.getConditions().filter(c => c.status === "True"); - } - - getWarningConditions() { - const goodConditions = ["Ready", "HostUpgrades", "SchedulingDisabled"]; - - return this.getActiveConditions().filter(condition => { - return !goodConditions.includes(condition.type); - }); - } - - getKubeletVersion() { - return this.status?.nodeInfo?.kubeletVersion ?? ""; - } - - getOperatingSystem(): string { - return this.metadata?.labels?.["kubernetes.io/os"] - || this.metadata?.labels?.["beta.kubernetes.io/os"] - || this.status?.nodeInfo?.operatingSystem - || "linux"; - } - - isUnschedulable() { - return this.spec.unschedulable; - } -} diff --git a/packages/core/src/common/k8s-api/endpoints/persistent-volume-claim.api.ts b/packages/core/src/common/k8s-api/endpoints/persistent-volume-claim.api.ts index 25e4d63454..ef4b5f1ba3 100644 --- a/packages/core/src/common/k8s-api/endpoints/persistent-volume-claim.api.ts +++ b/packages/core/src/common/k8s-api/endpoints/persistent-volume-claim.api.ts @@ -3,13 +3,9 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import type { LabelSelector, NamespaceScopedMetadata, TypedLocalObjectReference } from "../kube-object"; -import { KubeObject } from "../kube-object"; -import type { Pod } from "./pod.api"; import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api"; import { KubeApi } from "../kube-api"; -import { object } from "@k8slens/utilities"; -import type { ResourceRequirements } from "./types/resource-requirements"; +import { PersistentVolumeClaim } from "@k8slens/kube-object"; export class PersistentVolumeClaimApi extends KubeApi { constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) { @@ -20,54 +16,3 @@ export class PersistentVolumeClaimApi extends KubeApi { } } -export interface PersistentVolumeClaimSpec { - accessModes?: string[]; - dataSource?: TypedLocalObjectReference; - dataSourceRef?: TypedLocalObjectReference; - resources?: ResourceRequirements; - selector?: LabelSelector; - storageClassName?: string; - volumeMode?: string; - volumeName?: string; -} - -export interface PersistentVolumeClaimStatus { - phase: string; // Pending -} - -export class PersistentVolumeClaim extends KubeObject< - NamespaceScopedMetadata, - PersistentVolumeClaimStatus, - PersistentVolumeClaimSpec -> { - static readonly kind = "PersistentVolumeClaim"; - static readonly namespaced = true; - static readonly apiBase = "/api/v1/persistentvolumeclaims"; - - getPods(pods: Pod[]): Pod[] { - return pods - .filter(pod => pod.getNs() === this.getNs()) - .filter(pod => ( - pod.getVolumes() - .filter(volume => volume.persistentVolumeClaim?.claimName === this.getName()) - .length > 0 - )); - } - - getStorage(): string { - return this.spec.resources?.requests?.storage ?? "-"; - } - - getMatchLabels(): string[] { - return object.entries(this.spec.selector?.matchLabels) - .map(([name, val]) => `${name}:${val}`); - } - - getMatchExpressions() { - return this.spec.selector?.matchExpressions ?? []; - } - - getStatus(): string { - return this.status?.phase ?? "-"; - } -} diff --git a/packages/core/src/common/k8s-api/endpoints/persistent-volume.api.ts b/packages/core/src/common/k8s-api/endpoints/persistent-volume.api.ts index 6d6e249db5..2570c86cab 100644 --- a/packages/core/src/common/k8s-api/endpoints/persistent-volume.api.ts +++ b/packages/core/src/common/k8s-api/endpoints/persistent-volume.api.ts @@ -3,104 +3,9 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import type { ClusterScopedMetadata, LabelSelector, ObjectReference, TypedLocalObjectReference } from "../kube-object"; -import { KubeObject } from "../kube-object"; -import { unitsToBytes } from "@k8slens/utilities"; +import { PersistentVolume } from "@k8slens/kube-object"; import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api"; import { KubeApi } from "../kube-api"; -import type { ResourceRequirements } from "./types/resource-requirements"; - -export interface PersistentVolumeSpec { - /** - * AccessModes contains the desired access modes the volume should have. - * - * More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1 - */ - accessModes?: string[]; - dataSource?: TypedLocalObjectReference; - dataSourceRef?: TypedLocalObjectReference; - resources?: ResourceRequirements; - selector?: LabelSelector; - - /** - * Name of the StorageClass required by the claim. - * - * More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1 - */ - storageClassName?: string; - - /** - * Defines what type of volume is required by the claim. Value of Filesystem is implied when not - * included in claim spec. - */ - volumeMode?: string; - - /** - * A description of the persistent volume\'s resources and capacity. - * - * More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#capacity - */ - capacity?: Partial>; - flexVolume?: { - driver: string; // ceph.rook.io/rook-ceph-system, - options?: { - clusterNamespace: string; // rook-ceph, - image: string; // pvc-c5d7c485-9f1b-11e8-b0ea-9600000e54fb, - pool: string; // replicapool, - storageClass: string; // rook-ceph-block - }; - }; - mountOptions?: string[]; - claimRef?: ObjectReference; - persistentVolumeReclaimPolicy?: string; // Delete, - nfs?: { - path: string; - server: string; - }; -} - -export interface PersistentVolumeStatus { - phase: string; - reason?: string; -} - -export class PersistentVolume extends KubeObject< - ClusterScopedMetadata, - PersistentVolumeStatus, - PersistentVolumeSpec -> { - static kind = "PersistentVolume"; - static namespaced = false; - static apiBase = "/api/v1/persistentvolumes"; - - getCapacity(inBytes = false) { - const capacity = this.spec.capacity; - - if (capacity?.storage) { - if (inBytes) return unitsToBytes(capacity.storage); - - return capacity.storage; - } - - return 0; - } - - getStatus() { - return this.status?.phase || "-"; - } - - getStorageClass(): string { - return this.spec.storageClassName ?? ""; - } - - getClaimRefName(): string { - return this.spec.claimRef?.name ?? ""; - } - - getStorageClassName() { - return this.spec.storageClassName || ""; - } -} export class PersistentVolumeApi extends KubeApi { constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions = {}) { diff --git a/packages/core/src/common/k8s-api/endpoints/pod-disruption-budget.api.ts b/packages/core/src/common/k8s-api/endpoints/pod-disruption-budget.api.ts index 62cdaeb384..975e49e71c 100644 --- a/packages/core/src/common/k8s-api/endpoints/pod-disruption-budget.api.ts +++ b/packages/core/src/common/k8s-api/endpoints/pod-disruption-budget.api.ts @@ -3,78 +3,9 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import type { LabelSelector, NamespaceScopedMetadata } from "../kube-object"; -import { KubeObject } from "../kube-object"; +import { PodDisruptionBudget } from "@k8slens/kube-object"; import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api"; import { KubeApi } from "../kube-api"; -import type { Condition } from "./types/condition"; - -export interface V1Beta1PodDisruptionBudgetSpec { - minAvailable: string; - maxUnavailable: string; - selector: LabelSelector; -} - -export interface V1PodDisruptionBudgetSpec { - maxUnavailable?: string | number; - minAvailable?: string | number; - selector?: LabelSelector; -} - -export type PodDisruptionBudgetSpec = - | V1Beta1PodDisruptionBudgetSpec - | V1PodDisruptionBudgetSpec; - -export interface V1Beta1PodDisruptionBudgetStatus { - currentHealthy: number; - desiredHealthy: number; - disruptionsAllowed: number; - expectedPods: number; -} - -export interface V1PodDisruptionBudgetStatus { - conditions?: Condition[]; - currentHealthy: number; - desiredHealthy: number; - disruptedPods?: Partial>; - disruptionsAllowed: number; - expectedPods: number; - observedGeneration?: number; -} - -export type PodDisruptionBudgetStatus = - | V1Beta1PodDisruptionBudgetStatus - | V1PodDisruptionBudgetStatus; - -export class PodDisruptionBudget extends KubeObject< - NamespaceScopedMetadata, - PodDisruptionBudgetStatus, - PodDisruptionBudgetSpec -> { - static readonly kind = "PodDisruptionBudget"; - static readonly namespaced = true; - static readonly apiBase = "/apis/policy/v1beta1/poddisruptionbudgets"; - - getSelectors() { - return KubeObject.stringifyLabels(this.spec.selector?.matchLabels); - } - - getMinAvailable() { - return this.spec.minAvailable ?? "N/A"; - } - - getMaxUnavailable() { - return this.spec.maxUnavailable ?? "N/A"; - } - - getCurrentHealthy() { - return this.status?.currentHealthy ?? 0; - } - - getDesiredHealthy() { - return this.status?.desiredHealthy ?? 0; - } -} export class PodDisruptionBudgetApi extends KubeApi { constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions = {}) { diff --git a/packages/core/src/common/k8s-api/endpoints/pod-metrics.api.ts b/packages/core/src/common/k8s-api/endpoints/pod-metrics.api.ts index 84a58026d5..f13cfd9799 100644 --- a/packages/core/src/common/k8s-api/endpoints/pod-metrics.api.ts +++ b/packages/core/src/common/k8s-api/endpoints/pod-metrics.api.ts @@ -3,53 +3,10 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import type { KubeObjectMetadata, KubeObjectScope, NamespaceScopedMetadata } from "../kube-object"; -import { KubeObject } from "../kube-object"; +import type { PodMetricsData } from "@k8slens/kube-object"; +import { PodMetrics } from "@k8slens/kube-object"; import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api"; import { KubeApi } from "../kube-api"; -import type { KubeJsonApiData } from "../kube-json-api"; - -export interface PodMetricsData extends KubeJsonApiData, void, void> { - timestamp: string; - window: string; - containers: PodMetricsContainer[]; -} - -export interface PodMetricsContainerUsage { - cpu: string; - memory: string; -} - -export interface PodMetricsContainer { - name: string; - usage: PodMetricsContainerUsage; -} - -export class PodMetrics extends KubeObject< - NamespaceScopedMetadata, - void, - void -> { - static readonly kind = "PodMetrics"; - static readonly namespaced = true; - static readonly apiBase = "/apis/metrics.k8s.io/v1beta1/pods"; - - timestamp: string; - window: string; - containers: PodMetricsContainer[]; - - constructor({ - timestamp, - window, - containers, - ...rest - }: PodMetricsData) { - super(rest); - this.timestamp = timestamp; - this.window = window; - this.containers = containers; - } -} export class PodMetricsApi extends KubeApi { constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions = {}) { diff --git a/packages/core/src/common/k8s-api/endpoints/pod-security-policy.api.ts b/packages/core/src/common/k8s-api/endpoints/pod-security-policy.api.ts index f0e9e1110c..89f3e003f8 100644 --- a/packages/core/src/common/k8s-api/endpoints/pod-security-policy.api.ts +++ b/packages/core/src/common/k8s-api/endpoints/pod-security-policy.api.ts @@ -3,113 +3,10 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import type { ClusterScopedMetadata } from "../kube-object"; -import { KubeObject } from "../kube-object"; +import { PodSecurityPolicy } from "@k8slens/kube-object"; import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api"; import { KubeApi } from "../kube-api"; -export interface PodSecurityPolicySpec { - allowPrivilegeEscalation?: boolean; - allowedCSIDrivers?: { - name: string; - }[]; - allowedCapabilities: string[]; - allowedFlexVolumes?: { - driver: string; - }[]; - allowedHostPaths?: { - pathPrefix: string; - readOnly: boolean; - }[]; - allowedProcMountTypes?: string[]; - allowedUnsafeSysctls?: string[]; - defaultAddCapabilities?: string[]; - defaultAllowPrivilegeEscalation?: boolean; - forbiddenSysctls?: string[]; - fsGroup?: { - rule: string; - ranges: { - max: number; - min: number; - }[]; - }; - hostIPC?: boolean; - hostNetwork?: boolean; - hostPID?: boolean; - hostPorts?: { - max: number; - min: number; - }[]; - privileged?: boolean; - readOnlyRootFilesystem?: boolean; - requiredDropCapabilities?: string[]; - runAsGroup?: { - ranges: { - max: number; - min: number; - }[]; - rule: string; - }; - runAsUser?: { - rule: string; - ranges: { - max: number; - min: number; - }[]; - }; - runtimeClass?: { - allowedRuntimeClassNames: string[]; - defaultRuntimeClassName: string; - }; - seLinux?: { - rule: string; - seLinuxOptions: { - level: string; - role: string; - type: string; - user: string; - }; - }; - supplementalGroups?: { - rule: string; - ranges: { - max: number; - min: number; - }[]; - }; - volumes?: string[]; -} - -export class PodSecurityPolicy extends KubeObject< - ClusterScopedMetadata, - void, - PodSecurityPolicySpec -> { - static readonly kind = "PodSecurityPolicy"; - static readonly namespaced = false; - static readonly apiBase = "/apis/policy/v1beta1/podsecuritypolicies"; - - isPrivileged() { - return !!this.spec.privileged; - } - - getVolumes() { - return this.spec.volumes || []; - } - - getRules() { - const { fsGroup, runAsGroup, runAsUser, supplementalGroups, seLinux } = this.spec; - - return { - fsGroup: fsGroup ? fsGroup.rule : "", - runAsGroup: runAsGroup ? runAsGroup.rule : "", - runAsUser: runAsUser ? runAsUser.rule : "", - supplementalGroups: supplementalGroups ? supplementalGroups.rule : "", - seLinux: seLinux ? seLinux.rule : "", - }; - } -} - export class PodSecurityPolicyApi extends KubeApi { constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions = {}) { super(deps, { diff --git a/packages/core/src/common/k8s-api/endpoints/pod.api.ts b/packages/core/src/common/k8s-api/endpoints/pod.api.ts index a06389c20a..c63f970edf 100644 --- a/packages/core/src/common/k8s-api/endpoints/pod.api.ts +++ b/packages/core/src/common/k8s-api/endpoints/pod.api.ts @@ -10,22 +10,9 @@ import type { ResourceDescriptor, } from "../kube-api"; import { KubeApi } from "../kube-api"; -import type { RequireExactlyOne } from "type-fest"; -import type { - Affinity, - KubeObjectMetadata, KubeStatusData, - LocalObjectReference, - NamespaceScopedMetadata, - Toleration, -} from "../kube-object"; -import { isKubeStatusData, KubeObject, KubeStatus } from "../kube-object"; -import type { SecretReference } from "./secret.api"; -import type { PersistentVolumeClaimSpec } from "./persistent-volume-claim.api"; -import { isDefined } from "@k8slens/utilities"; -import type { PodSecurityContext } from "./types/pod-security-context"; -import type { Probe } from "./types/probe"; -import type { Container } from "./types/container"; -import type { ObjectFieldSelector, ResourceFieldSelector } from "./types"; +import type { KubeStatusData, PodLogsQuery } from "@k8slens/kube-object"; +import { isKubeStatusData, KubeStatus, Pod } from "@k8slens/kube-object"; + export class PodApi extends KubeApi { constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) { @@ -73,826 +60,3 @@ export class PodApi extends KubeApi { return this.request.get(path, { query }); } } - -// Reference: https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#read-log-pod-v1-core -export interface PodLogsQuery { - container?: string; - tailLines?: number; - timestamps?: boolean; - sinceTime?: string; // Date.toISOString()-format - follow?: boolean; - previous?: boolean; -} - -export enum PodStatusPhase { - TERMINATED = "Terminated", - FAILED = "Failed", - PENDING = "Pending", - RUNNING = "Running", - SUCCEEDED = "Succeeded", - EVICTED = "Evicted", -} - -export interface ContainerStateRunning { - startedAt: string; -} - -export interface ContainerStateWaiting { - reason: string; - message: string; -} - -export interface ContainerStateTerminated { - startedAt: string; - finishedAt: string; - exitCode: number; - reason: string; - containerID?: string; - message?: string; - signal?: number; -} - -/** - * ContainerState holds a possible state of container. Only one of its members - * may be specified. If none of them is specified, the default one is - * `ContainerStateWaiting`. - */ -export interface ContainerState { - running?: ContainerStateRunning; - waiting?: ContainerStateWaiting; - terminated?: ContainerStateTerminated; -} - -export type ContainerStateValues = Partial; - -export interface PodContainerStatus { - name: string; - state?: ContainerState; - lastState?: ContainerState; - ready: boolean; - restartCount: number; - image: string; - imageID: string; - containerID?: string; - started?: boolean; -} - -export interface AwsElasticBlockStoreSource { - volumeID: string; - fsType: string; -} - -export interface AzureDiskSource { - /** - * The name of the VHD blob object OR the name of an Azure managed data disk if `kind` is `"Managed"`. - */ - diskName: string; - /** - * The URI of the vhd blob object OR the `resourceID` of an Azure managed data disk if `kind` is `"Managed"`. - */ - diskURI: string; - /** - * Kind of disk - * @default "Shared" - */ - kind?: "Shared" | "Dedicated" | "Managed"; - /** - * Disk caching mode. - * @default "None" - */ - cachingMode?: "None" | "ReadOnly" | "ReadWrite"; - /** - * The filesystem type to mount. - * @default "ext4" - */ - fsType?: string; - /** - * Whether the filesystem is used as readOnly. - * @default false - */ - readonly?: boolean; -} - -export interface AzureFileSource { - /** - * The name of the secret that contains both Azure storage account name and key. - */ - secretName: string; - /** - * The share name to be used. - */ - shareName: string; - /** - * In case the secret is stored in a different namespace. - * @default "default" - */ - secretNamespace?: string; - /** - * Whether the filesystem is used as readOnly. - */ - readOnly: boolean; -} - -export interface CephfsSource { - /** - * List of Ceph monitors - */ - monitors: string[]; - /** - * Used as the mounted root, rather than the full Ceph tree. - * @default "/" - */ - path?: string; - /** - * The RADOS user name. - * @default "admin" - */ - user?: string; - /** - * The path to the keyring file. - * @default "/etc/ceph/user.secret" - */ - secretFile?: string; - /** - * Reference to Ceph authentication secrets. If provided, then the secret overrides `secretFile` - */ - secretRef?: SecretReference; - /** - * Whether the filesystem is used as readOnly. - * - * @default false - */ - readOnly?: boolean; -} - -export interface CinderSource { - volumeID: string; - fsType: string; - /** - * @default false - */ - readOnly?: boolean; - secretRef?: SecretReference; -} - -export interface ConfigMapSource { - name: string; - items: { - key: string; - path: string; - }[]; -} - -export interface DownwardApiSource { - items: { - path: string; - fieldRef: { - fieldPath: string; - }; - }[]; -} - -export interface EphemeralSource { - volumeClaimTemplate: { - /** - * All the rest of the fields are ignored and rejected during validation - */ - metadata?: Pick; - spec: PersistentVolumeClaimSpec; - }; -} - -export interface EmptyDirSource { - medium?: string; - sizeLimit?: string; -} - -export interface FiberChannelSource { - /** - * A list of World Wide Names - */ - targetWWNs: string[]; - /** - * Logical Unit number - */ - lun: number; - /** - * The type of filesystem - * @default "ext4" - */ - fsType?: string; - readOnly: boolean; -} - -export interface FlockerSource { - datasetName: string; -} - -export interface FlexVolumeSource { - driver: string; - fsType?: string; - secretRef?: LocalObjectReference; - /** - * @default false - */ - readOnly?: boolean; - options?: Record; -} - -export interface GcePersistentDiskSource { - pdName: string; - fsType: string; -} - -export interface GitRepoSource { - repository: string; - revision: string; -} - -export interface GlusterFsSource { - /** - * The name of the Endpoints object that represents a Gluster cluster configuration. - */ - endpoints: string; - /** - * The Glusterfs volume name. - */ - path: string; - /** - * The boolean that sets the mountpoint readOnly or readWrite. - */ - readOnly: boolean; -} - -export interface HostPathSource { - path: string; - /** - * Determines the sorts of checks that will be done - * @default "" - */ - type?: "" | "DirectoryOrCreate" | "Directory" | "FileOrCreate" | "File" | "Socket" | "CharDevice" | "BlockDevice"; -} - -export interface IScsiSource { - targetPortal: string; - iqn: string; - lun: number; - fsType: string; - readOnly: boolean; - chapAuthDiscovery?: boolean; - chapAuthSession?: boolean; - secretRef?: SecretReference; -} - -export interface LocalSource { - path: string; -} - -export interface NetworkFsSource { - server: string; - path: string; - readOnly?: boolean; -} - -export interface PersistentVolumeClaimSource { - claimName: string; -} - -export interface PhotonPersistentDiskSource { - pdID: string; - /** - * @default "ext4" - */ - fsType?: string; -} - -export interface PortworxVolumeSource { - volumeID: string; - fsType?: string; - readOnly?: boolean; -} - -export interface KeyToPath { - key: string; - path: string; - mode?: number; -} - -export interface ConfigMapProjection { - name: string; - items?: KeyToPath[]; - optional?: boolean; -} - -export interface DownwardAPIVolumeFile { - path: string; - fieldRef?: ObjectFieldSelector; - resourceFieldRef?: ResourceFieldSelector; - mode?: number; -} - -export interface DownwardAPIProjection { - items?: DownwardAPIVolumeFile[]; -} - -export interface SecretProjection { - name: string; - items?: KeyToPath[]; - optional?: boolean; -} - -export interface ServiceAccountTokenProjection { - audience?: string; - expirationSeconds?: number; - path: string; -} - -export interface VolumeProjection { - secret?: SecretProjection; - downwardAPI?: DownwardAPIProjection; - configMap?: ConfigMapProjection; - serviceAccountToken?: ServiceAccountTokenProjection; -} - -export interface ProjectedSource { - sources?: VolumeProjection[]; - defaultMode?: number; -} - -export interface QuobyteSource { - registry: string; - volume: string; - /** - * @default false - */ - readOnly?: boolean; - /** - * @default "serivceaccount" - */ - user?: string; - group?: string; - tenant?: string; -} - -export interface RadosBlockDeviceSource { - monitors: string[]; - image: string; - /** - * @default "ext4" - */ - fsType?: string; - /** - * @default "rbd" - */ - pool?: string; - /** - * @default "admin" - */ - user?: string; - /** - * @default "/etc/ceph/keyring" - */ - keyring?: string; - secretRef?: SecretReference; - /** - * @default false - */ - readOnly?: boolean; -} - -export interface ScaleIoSource { - gateway: string; - system: string; - secretRef?: LocalObjectReference; - /** - * @default false - */ - sslEnabled?: boolean; - protectionDomain?: string; - storagePool?: string; - /** - * @default "ThinProvisioned" - */ - storageMode?: "ThickProvisioned" | "ThinProvisioned"; - volumeName: string; - /** - * @default "xfs" - */ - fsType?: string; - /** - * @default false - */ - readOnly?: boolean; -} - -export interface SecretSource { - secretName: string; - items?: { - key: string; - path: string; - mode?: number; - }[]; - defaultMode?: number; - optional?: boolean; -} - -export interface StorageOsSource { - volumeName: string; - /** - * @default Pod.metadata.namespace - */ - volumeNamespace?: string; - /** - * @default "ext4" - */ - fsType?: string; - /** - * @default false - */ - readOnly?: boolean; - secretRef?: LocalObjectReference; -} - -export interface VsphereVolumeSource { - volumePath: string; - /** - * @default "ext4" - */ - fsType?: string; - storagePolicyName?: string; - storagePolicyID?: string; -} - -export interface ContainerStorageInterfaceSource { - driver: string; - /** - * @default false - */ - readOnly?: boolean; - /** - * @default "ext4" - */ - fsType?: string; - volumeAttributes?: Record; - controllerPublishSecretRef?: SecretReference; - nodeStageSecretRef?: SecretReference; - nodePublishSecretRef?: SecretReference; - controllerExpandSecretRef?: SecretReference; -} - -export interface PodVolumeVariants { - awsElasticBlockStore: AwsElasticBlockStoreSource; - azureDisk: AzureDiskSource; - azureFile: AzureFileSource; - cephfs: CephfsSource; - cinder: CinderSource; - configMap: ConfigMapSource; - csi: ContainerStorageInterfaceSource; - downwardAPI: DownwardApiSource; - emptyDir: EmptyDirSource; - ephemeral: EphemeralSource; - fc: FiberChannelSource; - flexVolume: FlexVolumeSource; - flocker: FlockerSource; - gcePersistentDisk: GcePersistentDiskSource; - gitRepo: GitRepoSource; - glusterfs: GlusterFsSource; - hostPath: HostPathSource; - iscsi: IScsiSource; - local: LocalSource; - nfs: NetworkFsSource; - persistentVolumeClaim: PersistentVolumeClaimSource; - photonPersistentDisk: PhotonPersistentDiskSource; - portworxVolume: PortworxVolumeSource; - projected: ProjectedSource; - quobyte: QuobyteSource; - rbd: RadosBlockDeviceSource; - scaleIO: ScaleIoSource; - secret: SecretSource; - storageos: StorageOsSource; - vsphereVolume: VsphereVolumeSource; -} - -/** - * The valid kinds of volume - */ -export type PodVolumeKind = keyof PodVolumeVariants; - -export type PodSpecVolume = RequireExactlyOne & { - name: string; -}; - - -export interface HostAlias { - ip: string; - hostnames: string[]; -} - -export interface Sysctl { - name: string; - value: string; -} - -export interface TopologySpreadConstraint { - -} - -export interface PodSpec { - activeDeadlineSeconds?: number; - affinity?: Affinity; - automountServiceAccountToken?: boolean; - containers?: Container[]; - dnsPolicy?: string; - enableServiceLinks?: boolean; - ephemeralContainers?: unknown[]; - hostAliases?: HostAlias[]; - hostIPC?: boolean; - hostname?: string; - hostNetwork?: boolean; - hostPID?: boolean; - imagePullSecrets?: LocalObjectReference[]; - initContainers?: Container[]; - nodeName?: string; - nodeSelector?: Partial>; - overhead?: Partial>; - preemptionPolicy?: string; - priority?: number; - priorityClassName?: string; - readinessGates?: unknown[]; - restartPolicy?: string; - runtimeClassName?: string; - schedulerName?: string; - securityContext?: PodSecurityContext; - serviceAccount?: string; - serviceAccountName?: string; - setHostnameAsFQDN?: boolean; - shareProcessNamespace?: boolean; - subdomain?: string; - terminationGracePeriodSeconds?: number; - tolerations?: Toleration[]; - topologySpreadConstraints?: TopologySpreadConstraint[]; - volumes?: PodSpecVolume[]; -} - -export interface PodCondition { - lastProbeTime?: number; - lastTransitionTime?: string; - message?: string; - reason?: string; - type: string; - status: string; -} - -export interface PodStatus { - phase: string; - conditions: PodCondition[]; - hostIP: string; - podIP: string; - podIPs?: { - ip: string; - }[]; - startTime: string; - initContainerStatuses?: PodContainerStatus[]; - containerStatuses?: PodContainerStatus[]; - qosClass?: string; - reason?: string; -} - -export class Pod extends KubeObject< - NamespaceScopedMetadata, - PodStatus, - PodSpec -> { - static kind = "Pod"; - static namespaced = true; - static apiBase = "/api/v1/pods"; - - getAffinityNumber() { - return Object.keys(this.getAffinity()).length; - } - - getInitContainers() { - return this.spec?.initContainers ?? []; - } - - getContainers() { - return this.spec?.containers ?? []; - } - - getAllContainers() { - return [...this.getContainers(), ...this.getInitContainers()]; - } - - getRunningContainers() { - const runningContainerNames = new Set( - this.getContainerStatuses() - .filter(({ state }) => state?.running) - .map(({ name }) => name), - ); - - return this.getAllContainers() - .filter(({ name }) => runningContainerNames.has(name)); - } - - getContainerStatuses(includeInitContainers = true): PodContainerStatus[] { - const { containerStatuses = [], initContainerStatuses = [] } = this.status ?? {}; - - if (includeInitContainers) { - return [...containerStatuses, ...initContainerStatuses]; - } - - return [...containerStatuses]; - } - - getRestartsCount(): number { - const { containerStatuses = [] } = this.status ?? {}; - - return containerStatuses.reduce((totalCount, { restartCount }) => totalCount + restartCount, 0); - } - - getQosClass() { - return this.status?.qosClass || ""; - } - - getReason() { - return this.status?.reason || ""; - } - - getPriorityClassName() { - return this.spec?.priorityClassName || ""; - } - - getRuntimeClassName() { - return this.spec?.runtimeClassName || ""; - } - - getServiceAccountName() { - return this.spec?.serviceAccountName || ""; - } - - getStatus(): PodStatusPhase { - const phase = this.getStatusPhase(); - const reason = this.getReason(); - const trueConditionTypes = new Set(this.getConditions() - .filter(({ status }) => status === "True") - .map(({ type }) => type)); - const isInGoodCondition = ["Initialized", "Ready"].every(condition => trueConditionTypes.has(condition)); - - if (reason === PodStatusPhase.EVICTED) { - return PodStatusPhase.EVICTED; - } - - if (phase === PodStatusPhase.FAILED) { - return PodStatusPhase.FAILED; - } - - if (phase === PodStatusPhase.SUCCEEDED) { - return PodStatusPhase.SUCCEEDED; - } - - if (phase === PodStatusPhase.RUNNING && isInGoodCondition) { - return PodStatusPhase.RUNNING; - } - - return PodStatusPhase.PENDING; - } - - // Returns pod phase or container error if occurred - getStatusMessage(): string { - if (this.getReason() === PodStatusPhase.EVICTED) { - return "Evicted"; - } - - if (this.metadata.deletionTimestamp) { - return "Terminating"; - } - - return this.getStatusPhase() || "Waiting"; - } - - getStatusPhase() { - return this.status?.phase; - } - - getConditions() { - return this.status?.conditions ?? []; - } - - getVolumes() { - return this.spec?.volumes ?? []; - } - - getSecrets(): string[] { - return this.getVolumes() - .map(vol => vol.secret?.secretName) - .filter(isDefined); - } - - getNodeSelectors(): string[] { - return Object.entries(this.spec?.nodeSelector ?? {}) - .map(values => values.join(": ")); - } - - getTolerations() { - return this.spec?.tolerations ?? []; - } - - getAffinity(): Affinity { - return this.spec?.affinity ?? {}; - } - - hasIssues() { - for (const { type, status } of this.getConditions()) { - if (type === "Ready" && status !== "True") { - return true; - } - } - - for (const { state } of this.getContainerStatuses()) { - if (state?.waiting?.reason === "CrashLookBackOff") { - return true; - } - } - - return this.getStatusPhase() !== "Running"; - } - - getLivenessProbe(container: Container) { - return this.getProbe(container, container.livenessProbe); - } - - getReadinessProbe(container: Container) { - return this.getProbe(container, container.readinessProbe); - } - - getStartupProbe(container: Container) { - return this.getProbe(container, container.startupProbe); - } - - private getProbe(container: Container, probe: Probe | undefined): string[] { - const probeItems: string[] = []; - - if (!probe) { - return probeItems; - } - - const { - httpGet, - exec, - tcpSocket, - initialDelaySeconds = 0, - timeoutSeconds = 0, - periodSeconds = 0, - successThreshold = 0, - failureThreshold = 0, - } = probe; - - // HTTP Request - if (httpGet) { - const { path = "", port, host = "", scheme = "HTTP" } = httpGet; - const resolvedPort = typeof port === "number" - ? port - // Try and find the port number associated witht the name or fallback to the name itself - : container.ports?.find(containerPort => containerPort.name === port)?.containerPort || port; - - probeItems.push( - "http-get", - `${scheme.toLowerCase()}://${host}:${resolvedPort}${path}`, - ); - } - - // Command - if (exec?.command) { - probeItems.push(`exec [${exec.command.join(" ")}]`); - } - - // TCP Probe - if (tcpSocket?.port) { - probeItems.push(`tcp-socket :${tcpSocket.port}`); - } - - probeItems.push( - `delay=${initialDelaySeconds}s`, - `timeout=${timeoutSeconds}s`, - `period=${periodSeconds}s`, - `#success=${successThreshold}`, - `#failure=${failureThreshold}`, - ); - - return probeItems; - } - - getNodeName(): string | undefined { - return this.spec?.nodeName; - } - - getSelectedNodeOs(): string | undefined { - return this.spec?.nodeSelector?.["kubernetes.io/os"] || this.spec?.nodeSelector?.["beta.kubernetes.io/os"]; - } - - getIPs(): string[] { - const podIPs = this.status?.podIPs ?? []; - - return podIPs.map(value => value.ip); - } -} diff --git a/packages/core/src/common/k8s-api/endpoints/priority-class.api.ts b/packages/core/src/common/k8s-api/endpoints/priority-class.api.ts index 1d181b6038..d5e3319d27 100644 --- a/packages/core/src/common/k8s-api/endpoints/priority-class.api.ts +++ b/packages/core/src/common/k8s-api/endpoints/priority-class.api.ts @@ -5,56 +5,9 @@ import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api"; import { KubeApi } from "../kube-api"; -import type { KubeJsonApiData } from "../kube-json-api"; -import type { ClusterScopedMetadata, KubeObjectMetadata, KubeObjectScope } from "../kube-object"; -import { KubeObject } from "../kube-object"; -import type { PreemptionPolicy } from "./types/preemption-policy"; +import type { PriorityClassData } from "@k8slens/kube-object"; +import { PriorityClass } from "@k8slens/kube-object"; -export interface PriorityClassData extends KubeJsonApiData, void, void> { - description?: string; - globalDefault?: boolean; - preemptionPolicy?: PreemptionPolicy; - value: number; -} - -export class PriorityClass extends KubeObject< - ClusterScopedMetadata, - void, - void -> { - static readonly kind = "PriorityClass"; - static readonly namespaced = false; - static readonly apiBase = "/apis/scheduling.k8s.io/v1/priorityclasses"; - - description?: string; - globalDefault?: boolean; - preemptionPolicy?: PreemptionPolicy; - value?: number; - - constructor({ description, globalDefault, preemptionPolicy, value, ...rest }: PriorityClassData) { - super(rest); - this.description = description; - this.globalDefault = globalDefault; - this.preemptionPolicy = preemptionPolicy; - this.value = value; - } - - getDescription() { - return this.description || ""; - } - - getGlobalDefault() { - return (this.globalDefault || false).toString(); - } - - getPreemptionPolicy() { - return this.preemptionPolicy || "PreemptLowerPriority"; - } - - getValue() { - return this.value; - } -} export class PriorityClassApi extends KubeApi { constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions = {}) { diff --git a/packages/core/src/common/k8s-api/endpoints/replica-set.api.ts b/packages/core/src/common/k8s-api/endpoints/replica-set.api.ts index 1fee553b8d..b3a81de7a4 100644 --- a/packages/core/src/common/k8s-api/endpoints/replica-set.api.ts +++ b/packages/core/src/common/k8s-api/endpoints/replica-set.api.ts @@ -3,11 +3,9 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api"; +import type { DerivedKubeApiOptions, KubeApiDependencies, NamespacedResourceDescriptor } from "../kube-api"; import { KubeApi } from "../kube-api"; -import type { KubeObjectStatus, LabelSelector, NamespaceScopedMetadata } from "../kube-object"; -import { KubeObject } from "../kube-object"; -import type { PodTemplateSpec } from "./types/pod-template-spec"; +import { ReplicaSet } from "@k8slens/kube-object"; export class ReplicaSetApi extends KubeApi { constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) { @@ -17,17 +15,17 @@ export class ReplicaSetApi extends KubeApi { }); } - protected getScaleApiUrl(params: { namespace: string; name: string }) { - return `${this.getUrl(params)}/scale`; + protected getScaleApiUrl(params: NamespacedResourceDescriptor) { + return `${this.formatUrlForNotListing(params)}/scale`; } - async getReplicas(params: { namespace: string; name: string }): Promise { + async getReplicas(params: NamespacedResourceDescriptor): Promise { const { status } = await this.request.get(this.getScaleApiUrl(params)); return (status as { replicas: number })?.replicas; } - scale(params: { namespace: string; name: string }, replicas: number) { + scale(params: NamespacedResourceDescriptor, replicas: number) { return this.request.put(this.getScaleApiUrl(params), { data: { metadata: params, @@ -38,70 +36,3 @@ export class ReplicaSetApi extends KubeApi { }); } } - -export interface ReplicaSetSpec { - replicas?: number; - selector: LabelSelector; - template?: PodTemplateSpec; - minReadySeconds?: number; -} - -export interface ReplicaSetStatus extends KubeObjectStatus { - replicas: number; - fullyLabeledReplicas?: number; - readyReplicas?: number; - availableReplicas?: number; - observedGeneration?: number; -} - -export class ReplicaSet extends KubeObject< - NamespaceScopedMetadata, - ReplicaSetStatus, - ReplicaSetSpec -> { - static kind = "ReplicaSet"; - static namespaced = true; - static apiBase = "/apis/apps/v1/replicasets"; - - getSelectors(): string[] { - return KubeObject.stringifyLabels(this.spec.selector.matchLabels); - } - - getNodeSelectors(): string[] { - return KubeObject.stringifyLabels(this.spec.template?.spec?.nodeSelector); - } - - getTemplateLabels(): string[] { - return KubeObject.stringifyLabels(this.spec.template?.metadata?.labels); - } - - getTolerations() { - return this.spec.template?.spec?.tolerations ?? []; - } - - getAffinity() { - return this.spec.template?.spec?.affinity; - } - - getAffinityNumber() { - return Object.keys(this.getAffinity() ?? {}).length; - } - - getDesired() { - return this.spec.replicas ?? 0; - } - - getCurrent() { - return this.status?.availableReplicas ?? 0; - } - - getReady() { - return this.status?.readyReplicas ?? 0; - } - - getImages() { - const containers = this.spec.template?.spec?.containers ?? []; - - return containers.map(container => container.image); - } -} diff --git a/packages/core/src/common/k8s-api/endpoints/replication-controller.api.ts b/packages/core/src/common/k8s-api/endpoints/replication-controller.api.ts index b87b7a81a8..e7ef4d506d 100644 --- a/packages/core/src/common/k8s-api/endpoints/replication-controller.api.ts +++ b/packages/core/src/common/k8s-api/endpoints/replication-controller.api.ts @@ -3,15 +3,10 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api"; +import type { DerivedKubeApiOptions, KubeApiDependencies, NamespacedResourceDescriptor } from "../kube-api"; import { KubeApi } from "../kube-api"; -import type { - BaseKubeObjectCondition, KubeObjectMetadata, - KubeObjectStatus, - NamespaceScopedMetadata, -} from "../kube-object"; -import { KubeObject } from "../kube-object"; -import type { PodTemplateSpec } from "./types"; +import { ReplicationController } from "@k8slens/kube-object"; +import type { Scale } from "@k8slens/kube-object"; export class ReplicationControllerApi extends KubeApi { constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) { @@ -21,15 +16,15 @@ export class ReplicationControllerApi extends KubeApi { }); } - protected getScaleApiUrl(params: { namespace: string; name: string }) { + protected getScaleApiUrl(params: NamespacedResourceDescriptor) { return `${this.formatUrlForNotListing(params)}/scale`; } - getScale(params: { namespace: string; name: string }): Promise { + getScale(params: NamespacedResourceDescriptor): Promise { return this.request.get(this.getScaleApiUrl(params)); } - scale(params: { namespace: string; name: string }, replicas: number): Promise { + scale(params: NamespacedResourceDescriptor, replicas: number): Promise { return this.request.patch(this.getScaleApiUrl(params), { data: { metadata: params, @@ -44,106 +39,3 @@ export class ReplicationControllerApi extends KubeApi { }); } } - -export interface Scale { - apiVersion: "autoscaling/v1"; - kind: "Scale"; - metadata: KubeObjectMetadata; - spec: { - replicas: number; - }; - status: { - replicas: number; - selector: string; - }; -} - -export interface ReplicationControllerSpec { - /** - * Minimum number of seconds for which a newly created pod should be ready without any of its container crashing, for it to be considered available. - * Defaults to 0 (pod will be considered available as soon as it is ready) - */ - minReadySeconds?: number; - /** - * Replicas is the number of desired replicas. This is a pointer to distinguish between explicit zero and unspecified. - * Defaults to 1. More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller#what-is-a-replicationcontroller - */ - replicas?: number; - /** - * Selector is a label query over pods that should match the Replicas count. If Selector is empty, it is defaulted to the labels present on the Pod template. - * Label keys and values that must match in order to be controlled by this replication controller, if empty defaulted to labels on Pod template. - * More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors - */ - selector?: Record; - /** - * Template is the object that describes the pod that will be created if insufficient replicas are detected. This takes precedence over a TemplateRef. - * More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller#pod-template - */ - template: PodTemplateSpec; -} - -export interface ReplicationControllerStatus extends KubeObjectStatus { - /** - * The number of available replicas (ready for at least minReadySeconds) for this replication controller. - */ - availableReplicas: number; - /** - * The number of pods that have labels matching the labels of the pod template of the replication controller. - */ - fullyLabeledReplicas: number; - /** - * ObservedGeneration reflects the generation of the most recently observed replication controller. - */ - observedGeneration: number; - /** - * The number of ready replicas for this replication controller. - */ - readyReplicas: number; - /** - * Replicas is the most recently observed number of replicas. - * More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller#what-is-a-replicationcontroller - */ - replicas: number; -} - -export class ReplicationController extends KubeObject< - NamespaceScopedMetadata, - ReplicationControllerStatus, - ReplicationControllerSpec -> { - static kind = "ReplicationController"; - static namespaced = true; - static apiBase = "/api/v1/replicationcontrollers"; - - getMinReadySeconds(): number { - return this.spec?.minReadySeconds ?? 0; - } - - getGeneration() { - return this.status?.observedGeneration; - } - - getSelectorLabels(): string[] { - return KubeObject.stringifyLabels(this.spec.selector); - } - - getReplicas(): number | undefined { - return this.status?.replicas; - } - - getDesiredReplicas(): number { - return this.spec?.replicas ?? 0; - } - - getAvailableReplicas(): number | undefined { - return this.status?.availableReplicas; - } - - getLabeledReplicas(): number | undefined { - return this.status?.fullyLabeledReplicas; - } - - getConditions(): BaseKubeObjectCondition[] { - return this.status?.conditions ?? []; - } -} diff --git a/packages/core/src/common/k8s-api/endpoints/resource-applier.api/request-patch.injectable.ts b/packages/core/src/common/k8s-api/endpoints/resource-applier.api/request-patch.injectable.ts index 714340c78a..cd07058da8 100644 --- a/packages/core/src/common/k8s-api/endpoints/resource-applier.api/request-patch.injectable.ts +++ b/packages/core/src/common/k8s-api/endpoints/resource-applier.api/request-patch.injectable.ts @@ -6,7 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable"; import type { Patch } from "rfc6902"; import apiBaseInjectable from "../../api-base.injectable"; import type { AsyncResult, Result } from "@k8slens/utilities"; -import type { KubeJsonApiData } from "../../kube-json-api"; +import type { KubeJsonApiData } from "@k8slens/kube-object"; export type RequestKubeObjectPatch = (name: string, kind: string, ns: string | undefined, patch: Patch) => AsyncResult; @@ -16,21 +16,21 @@ const requestKubeObjectPatchInjectable = getInjectable({ const apiBase = di.inject(apiBaseInjectable); return async (name, kind, ns, patch) => { - const result = await apiBase.patch("/stack", { + const result = (await apiBase.patch("/stack", { data: { name, kind, ns, patch, }, - }) as Result; + })) as Result; if (!result.callWasSuccessful) { return result; } try { - const response = JSON.parse(result.response); + const response = JSON.parse(result.response) as KubeJsonApiData; return { callWasSuccessful: true, diff --git a/packages/core/src/common/k8s-api/endpoints/resource-applier.api/request-update.injectable.ts b/packages/core/src/common/k8s-api/endpoints/resource-applier.api/request-update.injectable.ts index 211ada1575..5e094c7a80 100644 --- a/packages/core/src/common/k8s-api/endpoints/resource-applier.api/request-update.injectable.ts +++ b/packages/core/src/common/k8s-api/endpoints/resource-applier.api/request-update.injectable.ts @@ -5,7 +5,7 @@ import { getInjectable } from "@ogre-tools/injectable"; import apiBaseInjectable from "../../api-base.injectable"; import type { AsyncResult, Result } from "@k8slens/utilities"; -import type { KubeJsonApiData } from "../../kube-json-api"; +import type { KubeJsonApiData } from "@k8slens/kube-object"; export type RequestKubeObjectCreation = (resourceDescriptor: string) => AsyncResult; diff --git a/packages/core/src/common/k8s-api/endpoints/resource-quota.api.ts b/packages/core/src/common/k8s-api/endpoints/resource-quota.api.ts index 9316f9d932..4edd967350 100644 --- a/packages/core/src/common/k8s-api/endpoints/resource-quota.api.ts +++ b/packages/core/src/common/k8s-api/endpoints/resource-quota.api.ts @@ -3,67 +3,10 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import type { NamespaceScopedMetadata } from "../kube-object"; -import { KubeObject } from "../kube-object"; +import { ResourceQuota } from "@k8slens/kube-object"; import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api"; import { KubeApi } from "../kube-api"; -export type IResourceQuotaValues = Partial> & { - // Compute Resource Quota - "limits.cpu"?: string; - "limits.memory"?: string; - "requests.cpu"?: string; - "requests.memory"?: string; - - // Storage Resource Quota - "requests.storage"?: string; - "persistentvolumeclaims"?: string; - - // Object Count Quota - "count/pods"?: string; - "count/persistentvolumeclaims"?: string; - "count/services"?: string; - "count/secrets"?: string; - "count/configmaps"?: string; - "count/replicationcontrollers"?: string; - "count/deployments.apps"?: string; - "count/replicasets.apps"?: string; - "count/statefulsets.apps"?: string; - "count/jobs.batch"?: string; - "count/cronjobs.batch"?: string; - "count/deployments.extensions"?: string; -}; - -export interface ResourceQuotaSpec { - hard: IResourceQuotaValues; - scopeSelector?: { - matchExpressions: { - operator: string; - scopeName: string; - values: string[]; - }[]; - }; -} - -export interface ResourceQuotaStatus { - hard: IResourceQuotaValues; - used: IResourceQuotaValues; -} - -export class ResourceQuota extends KubeObject< - NamespaceScopedMetadata, - ResourceQuotaStatus, - ResourceQuotaSpec -> { - static readonly kind = "ResourceQuota"; - static readonly namespaced = true; - static readonly apiBase = "/api/v1/resourcequotas"; - - getScopeSelector() { - return this.spec.scopeSelector?.matchExpressions ?? []; - } -} - export class ResourceQuotaApi extends KubeApi { constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions = {}) { super(deps, { diff --git a/packages/core/src/common/k8s-api/endpoints/role-binding.api.ts b/packages/core/src/common/k8s-api/endpoints/role-binding.api.ts index 3923efee89..49c92ed42b 100644 --- a/packages/core/src/common/k8s-api/endpoints/role-binding.api.ts +++ b/packages/core/src/common/k8s-api/endpoints/role-binding.api.ts @@ -3,45 +3,11 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import type { KubeObjectMetadata, KubeObjectScope, NamespaceScopedMetadata } from "../kube-object"; -import { KubeObject } from "../kube-object"; +import type { RoleBindingData } from "@k8slens/kube-object"; +import { RoleBinding } from "@k8slens/kube-object"; import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api"; import { KubeApi } from "../kube-api"; -import type { KubeJsonApiData } from "../kube-json-api"; -import type { RoleRef } from "./types/role-ref"; -import type { Subject } from "./types/subject"; -export interface RoleBindingData extends KubeJsonApiData, void, void> { - subjects?: Subject[]; - roleRef: RoleRef; -} - -export class RoleBinding extends KubeObject< - NamespaceScopedMetadata, - void, - void -> { - static readonly kind = "RoleBinding"; - static readonly namespaced = true; - static readonly apiBase = "/apis/rbac.authorization.k8s.io/v1/rolebindings"; - - subjects?: Subject[]; - roleRef: RoleRef; - - constructor({ subjects, roleRef, ...rest }: RoleBindingData) { - super(rest); - this.subjects = subjects; - this.roleRef = roleRef; - } - - getSubjects() { - return this.subjects || []; - } - - getSubjectNames(): string { - return this.getSubjects().map(subject => subject.name).join(", "); - } -} export class RoleBindingApi extends KubeApi { constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions = {}) { diff --git a/packages/core/src/common/k8s-api/endpoints/role.api.ts b/packages/core/src/common/k8s-api/endpoints/role.api.ts index d6bd0e79d3..1585922069 100644 --- a/packages/core/src/common/k8s-api/endpoints/role.api.ts +++ b/packages/core/src/common/k8s-api/endpoints/role.api.ts @@ -3,36 +3,10 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import type { KubeObjectMetadata, KubeObjectScope, NamespaceScopedMetadata } from "../kube-object"; -import { KubeObject } from "../kube-object"; +import type { RoleData } from "@k8slens/kube-object"; +import { Role } from "@k8slens/kube-object"; import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api"; import { KubeApi } from "../kube-api"; -import type { KubeJsonApiData } from "../kube-json-api"; -import type { PolicyRule } from "./types/policy-rule"; - -export interface RoleData extends KubeJsonApiData, void, void> { - rules?: PolicyRule[]; -} - -export class Role extends KubeObject< - NamespaceScopedMetadata, - void, - void -> { - static readonly kind = "Role"; - static readonly namespaced = true; - static readonly apiBase = "/apis/rbac.authorization.k8s.io/v1/roles"; - rules?: PolicyRule[]; - - constructor({ rules, ...rest }: RoleData) { - super(rest); - this.rules = rules; - } - - getRules() { - return this.rules || []; - } -} export class RoleApi extends KubeApi { constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions = {}) { diff --git a/packages/core/src/common/k8s-api/endpoints/runtime-class.api.ts b/packages/core/src/common/k8s-api/endpoints/runtime-class.api.ts index 16e3cefab5..1d3258ee4d 100644 --- a/packages/core/src/common/k8s-api/endpoints/runtime-class.api.ts +++ b/packages/core/src/common/k8s-api/endpoints/runtime-class.api.ts @@ -5,62 +5,8 @@ import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api"; import { KubeApi } from "../kube-api"; -import type { KubeJsonApiData } from "../kube-json-api"; -import type { ClusterScopedMetadata, KubeObjectMetadata, KubeObjectScope, Toleration } from "../kube-object"; -import { KubeObject } from "../kube-object"; - -export interface RuntimeClassData extends KubeJsonApiData, void, void> { - handler: string; - overhead?: RuntimeClassOverhead; - scheduling?: RuntimeClassScheduling; -} - -export interface RuntimeClassOverhead { - podFixed?: string; -} - -export interface RuntimeClassScheduling { - nodeSelector?: Partial>; - tolerations?: Toleration[]; -} - -export class RuntimeClass extends KubeObject< - ClusterScopedMetadata, - void, - void -> { - static readonly kind = "RuntimeClass"; - static readonly namespaced = false; - static readonly apiBase = "/apis/node.k8s.io/v1/runtimeclasses"; - - handler: string; - overhead?: RuntimeClassOverhead; - scheduling?: RuntimeClassScheduling; - - constructor({ handler, overhead, scheduling, ...rest }: RuntimeClassData) { - super(rest); - this.handler = handler; - this.overhead = overhead; - this.scheduling = scheduling; - } - - getHandler() { - return this.handler; - } - - getPodFixed() { - return this.overhead?.podFixed ?? ""; - } - - getNodeSelectors(): string[] { - return Object.entries(this.scheduling?.nodeSelector ?? {}) - .map(values => values.join(": ")); - } - - getTolerations() { - return this.scheduling?.tolerations ?? []; - } -} +import type { RuntimeClassData } from "@k8slens/kube-object"; +import { RuntimeClass } from "@k8slens/kube-object"; export class RuntimeClassApi extends KubeApi { constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions = {}) { diff --git a/packages/core/src/common/k8s-api/endpoints/secret.api.ts b/packages/core/src/common/k8s-api/endpoints/secret.api.ts index 213aa1ef2b..393e0d6a03 100644 --- a/packages/core/src/common/k8s-api/endpoints/secret.api.ts +++ b/packages/core/src/common/k8s-api/endpoints/secret.api.ts @@ -3,73 +3,10 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import type { KubeObjectMetadata, KubeObjectScope, NamespaceScopedMetadata } from "../kube-object"; -import { KubeObject } from "../kube-object"; -import type { KubeJsonApiData } from "../kube-json-api"; import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api"; import { KubeApi } from "../kube-api"; -import autoBind from "auto-bind"; - -export enum SecretType { - Opaque = "Opaque", - ServiceAccountToken = "kubernetes.io/service-account-token", - Dockercfg = "kubernetes.io/dockercfg", - DockerConfigJson = "kubernetes.io/dockerconfigjson", - BasicAuth = "kubernetes.io/basic-auth", - SSHAuth = "kubernetes.io/ssh-auth", - TLS = "kubernetes.io/tls", - BootstrapToken = "bootstrap.kubernetes.io/token", -} - -export const reverseSecretTypeMap = { - [SecretType.Opaque]: "Opaque", - [SecretType.ServiceAccountToken]: "ServiceAccountToken", - [SecretType.Dockercfg]: "Dockercfg", - [SecretType.DockerConfigJson]: "DockerConfigJson", - [SecretType.BasicAuth]: "BasicAuth", - [SecretType.SSHAuth]: "SSHAuth", - [SecretType.TLS]: "TLS", - [SecretType.BootstrapToken]: "BootstrapToken", -}; - -export interface SecretReference { - name: string; - namespace?: string; -} - -export interface SecretData extends KubeJsonApiData, void, void> { - type: SecretType; - data?: Partial>; -} - -export class Secret extends KubeObject< - NamespaceScopedMetadata, - void, - void -> { - static readonly kind = "Secret"; - static readonly namespaced = true; - static readonly apiBase = "/api/v1/secrets"; - - type: SecretType; - data: Partial>; - - constructor({ data = {}, type, ...rest }: SecretData) { - super(rest); - autoBind(this); - - this.data = data; - this.type = type; - } - - getKeys(): string[] { - return Object.keys(this.data); - } - - getToken() { - return this.data.token; - } -} +import { Secret } from "@k8slens/kube-object"; +import type { SecretData } from "@k8slens/kube-object"; export class SecretApi extends KubeApi { constructor(deps: KubeApiDependencies, options: DerivedKubeApiOptions = {}) { diff --git a/packages/core/src/common/k8s-api/endpoints/self-subject-rules-reviews.api.ts b/packages/core/src/common/k8s-api/endpoints/self-subject-rules-reviews.api.ts index 2c73d89e77..d1e9c7ad75 100644 --- a/packages/core/src/common/k8s-api/endpoints/self-subject-rules-reviews.api.ts +++ b/packages/core/src/common/k8s-api/endpoints/self-subject-rules-reviews.api.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import { KubeObject } from "../kube-object"; +import { SelfSubjectRulesReview } from "@k8slens/kube-object"; import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api"; import { KubeApi } from "../kube-api"; @@ -23,58 +23,3 @@ export class SelfSubjectRulesReviewApi extends KubeApi { }); } } - -export interface ISelfSubjectReviewRule { - verbs: string[]; - apiGroups?: string[]; - resources?: string[]; - resourceNames?: string[]; - nonResourceURLs?: string[]; -} - -export interface SelfSubjectRulesReview { - spec: { - namespace?: string; - }; - status: { - resourceRules: ISelfSubjectReviewRule[]; - nonResourceRules: ISelfSubjectReviewRule[]; - incomplete: boolean; - }; -} - -export class SelfSubjectRulesReview extends KubeObject { - static kind = "SelfSubjectRulesReview"; - static namespaced = false; - static apiBase = "/apis/authorization.k8s.io/v1/selfsubjectrulesreviews"; - - getResourceRules() { - const rules = this.status && this.status.resourceRules || []; - - return rules.map(rule => this.normalize(rule)); - } - - getNonResourceRules() { - const rules = this.status && this.status.nonResourceRules || []; - - return rules.map(rule => this.normalize(rule)); - } - - protected normalize(rule: ISelfSubjectReviewRule): ISelfSubjectReviewRule { - const { apiGroups = [], resourceNames = [], verbs = [], nonResourceURLs = [], resources = [] } = rule; - - return { - apiGroups, - nonResourceURLs, - resourceNames, - verbs, - resources: resources.map((resource, index) => { - const apiGroup = apiGroups.length >= index + 1 ? apiGroups[index] : apiGroups.slice(-1)[0]; - const separator = apiGroup == "" ? "" : "."; - - return resource + separator + apiGroup; - }), - }; - } -} - diff --git a/packages/core/src/common/k8s-api/endpoints/service-account.api.ts b/packages/core/src/common/k8s-api/endpoints/service-account.api.ts index e92ea667d9..9b570eca2c 100644 --- a/packages/core/src/common/k8s-api/endpoints/service-account.api.ts +++ b/packages/core/src/common/k8s-api/endpoints/service-account.api.ts @@ -3,51 +3,10 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import type { KubeObjectMetadata, KubeObjectScope, LocalObjectReference, NamespaceScopedMetadata, ObjectReference } from "../kube-object"; -import { KubeObject } from "../kube-object"; +import type { ServiceAccountData } from "@k8slens/kube-object"; +import { ServiceAccount } from "@k8slens/kube-object"; import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api"; import { KubeApi } from "../kube-api"; -import type { KubeJsonApiData } from "../kube-json-api"; - -export interface ServiceAccountData extends KubeJsonApiData, void, void> { - automountServiceAccountToken?: boolean; - imagePullSecrets?: LocalObjectReference[]; - secrets?: ObjectReference[]; -} - -export class ServiceAccount extends KubeObject< - NamespaceScopedMetadata, - void, - void -> { - static readonly kind = "ServiceAccount"; - static readonly namespaced = true; - static readonly apiBase = "/api/v1/serviceaccounts"; - - automountServiceAccountToken?: boolean; - imagePullSecrets?: LocalObjectReference[]; - secrets?: ObjectReference[]; - - constructor({ - automountServiceAccountToken, - imagePullSecrets, - secrets, - ...rest - }: ServiceAccountData) { - super(rest); - this.automountServiceAccountToken = automountServiceAccountToken; - this.imagePullSecrets = imagePullSecrets; - this.secrets = secrets; - } - - getSecrets() { - return this.secrets || []; - } - - getImagePullSecrets() { - return this.imagePullSecrets || []; - } -} export class ServiceAccountApi extends KubeApi { constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions = {}) { diff --git a/packages/core/src/common/k8s-api/endpoints/service.api.ts b/packages/core/src/common/k8s-api/endpoints/service.api.ts index f574bea53d..c8da51bb9a 100644 --- a/packages/core/src/common/k8s-api/endpoints/service.api.ts +++ b/packages/core/src/common/k8s-api/endpoints/service.api.ts @@ -3,131 +3,10 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import type { NamespaceScopedMetadata } from "../kube-object"; -import { KubeObject } from "../kube-object"; +import { Service } from "@k8slens/kube-object"; import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api"; import { KubeApi } from "../kube-api"; -export interface ServicePort { - name?: string; - protocol: string; - port: number; - targetPort: number; - nodePort?: number; -} - -export class ServicePort { - constructor(data: ServicePort) { - Object.assign(this, data); - } - - toString() { - if (this.nodePort) { - return `${this.port}:${this.nodePort}/${this.protocol}`; - } else { - return `${this.port}${this.port === this.targetPort ? "" : `:${this.targetPort}`}/${this.protocol}`; - } - } -} - -export interface ServiceSpec { - type: string; - clusterIP: string; - clusterIPs?: string[]; - externalTrafficPolicy?: string; - externalName?: string; - loadBalancerIP?: string; - loadBalancerSourceRanges?: string[]; - sessionAffinity: string; - selector: Partial>; - ports: ServicePort[]; - healthCheckNodePort?: number; - externalIPs?: string[]; // https://kubernetes.io/docs/concepts/services-networking/service/#external-ips - topologyKeys?: string[]; - ipFamilies?: string[]; - ipFamilyPolicy?: string; - allocateLoadBalancerNodePorts?: boolean; - loadBalancerClass?: string; - internalTrafficPolicy?: string; -} - -export interface ServiceStatus { - loadBalancer?: { - ingress?: { - ip?: string; - hostname?: string; - }[]; - }; -} - -export class Service extends KubeObject< - NamespaceScopedMetadata, - ServiceStatus, - ServiceSpec -> { - static readonly kind = "Service"; - static readonly namespaced = true; - static readonly apiBase = "/api/v1/services"; - - getClusterIp() { - return this.spec.clusterIP; - } - - getClusterIps() { - return this.spec.clusterIPs || []; - } - - getExternalIps() { - const lb = this.getLoadBalancer(); - - if (lb?.ingress) { - return lb.ingress.map(val => val.ip || val.hostname); - } - - if (Array.isArray(this.spec?.externalIPs)) { - return this.spec.externalIPs; - } - - return []; - } - - getType() { - return this.spec.type || "-"; - } - - getSelector(): string[] { - if (!this.spec.selector) return []; - - return Object.entries(this.spec.selector).map(val => val.join("=")); - } - - getPorts(): ServicePort[] { - const ports = this.spec.ports || []; - - return ports.map(p => new ServicePort(p)); - } - - getLoadBalancer() { - return this.status?.loadBalancer; - } - - isActive() { - return this.getType() !== "LoadBalancer" || this.getExternalIps().length > 0; - } - - getStatus() { - return this.isActive() ? "Active" : "Pending"; - } - - getIpFamilies() { - return this.spec.ipFamilies || []; - } - - getIpFamilyPolicy() { - return this.spec.ipFamilyPolicy || ""; - } -} - export class ServiceApi extends KubeApi { constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions = {}) { super(deps, { diff --git a/packages/core/src/common/k8s-api/endpoints/stateful-set.api.ts b/packages/core/src/common/k8s-api/endpoints/stateful-set.api.ts index e91a856262..5531455d2c 100644 --- a/packages/core/src/common/k8s-api/endpoints/stateful-set.api.ts +++ b/packages/core/src/common/k8s-api/endpoints/stateful-set.api.ts @@ -5,12 +5,9 @@ import moment from "moment"; -import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api"; +import type { DerivedKubeApiOptions, KubeApiDependencies, NamespacedResourceDescriptor } from "../kube-api"; import { KubeApi } from "../kube-api"; -import type { LabelSelector, NamespaceScopedMetadata } from "../kube-object"; -import { KubeObject } from "../kube-object"; -import type { PodTemplateSpec } from "./types/pod-template-spec"; -import type { PersistentVolumeClaimTemplateSpec } from "./types/persistent-volume-claim-template-spec"; +import { StatefulSet } from "@k8slens/kube-object"; export class StatefulSetApi extends KubeApi { constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) { @@ -20,109 +17,34 @@ export class StatefulSetApi extends KubeApi { }); } - protected getScaleApiUrl(params: { namespace: string; name: string }) { - return `${this.getUrl(params)}/scale`; + protected getScaleApiUrl(params: NamespacedResourceDescriptor) { + return `${this.formatUrlForNotListing(params)}/scale`; } - getReplicas(params: { namespace: string; name: string }): Promise { - return this.request - .get(this.getScaleApiUrl(params)) - .then(({ status }: any) => status?.replicas); + async getReplicas(params: NamespacedResourceDescriptor): Promise { + const apiUrl = this.getScaleApiUrl(params); + const { status = 0 } = await this.request.get(apiUrl) as { status?: number }; + + return status; } - scale(params: { namespace: string; name: string }, replicas: number) { - return this.request.patch(this.getScaleApiUrl(params), { - data: { - spec: { - replicas, - }, + scale(params: NamespacedResourceDescriptor, replicas: number) { + return this.patch(params, { + spec: { + replicas, }, - }, - { - headers: { - "content-type": "application/merge-patch+json", - }, - }); + }, "merge"); } - restart(params: { namespace: string; name: string }) { - return this.request.patch(this.getUrl(params), { - data: { - spec: { - template: { - metadata: { - annotations: { "kubectl.kubernetes.io/restartedAt" : moment.utc().format() }, - }, + restart(params: NamespacedResourceDescriptor) { + return this.patch(params, { + spec: { + template: { + metadata: { + annotations: { "kubectl.kubernetes.io/restartedAt" : moment.utc().format() }, }, }, }, - }, - { - headers: { - "content-type": "application/strategic-merge-patch+json", - }, - }); - } -} - -export interface StatefulSetSpec { - serviceName: string; - replicas: number; - selector: LabelSelector; - template: PodTemplateSpec; - volumeClaimTemplates: PersistentVolumeClaimTemplateSpec[]; -} - -export interface StatefulSetStatus { - observedGeneration: number; - replicas: number; - currentReplicas: number; - readyReplicas: number; - currentRevision: string; - updateRevision: string; - collisionCount: number; -} - -export class StatefulSet extends KubeObject< - NamespaceScopedMetadata, - StatefulSetStatus, - StatefulSetSpec -> { - static readonly kind = "StatefulSet"; - static readonly namespaced = true; - static readonly apiBase = "/apis/apps/v1/statefulsets"; - - getSelectors(): string[] { - return KubeObject.stringifyLabels(this.spec.selector.matchLabels); - } - - getNodeSelectors(): string[] { - return KubeObject.stringifyLabels(this.spec.template.spec?.nodeSelector); - } - - getTemplateLabels(): string[] { - return KubeObject.stringifyLabels(this.spec.template.metadata?.labels); - } - - getTolerations() { - return this.spec.template.spec?.tolerations ?? []; - } - - getAffinity() { - return this.spec.template.spec?.affinity ?? {}; - } - - getAffinityNumber() { - return Object.keys(this.getAffinity()).length; - } - - getReplicas() { - return this.spec.replicas || 0; - } - - getImages() { - const containers = this.spec.template?.spec?.containers ?? []; - - return containers.map(container => container.image); + }, "strategic"); } } diff --git a/packages/core/src/common/k8s-api/endpoints/storage-class.api.ts b/packages/core/src/common/k8s-api/endpoints/storage-class.api.ts index d0b7167876..08831201f3 100644 --- a/packages/core/src/common/k8s-api/endpoints/storage-class.api.ts +++ b/packages/core/src/common/k8s-api/endpoints/storage-class.api.ts @@ -3,87 +3,11 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import type { ClusterScopedMetadata, KubeObjectMetadata, KubeObjectScope } from "../kube-object"; -import { KubeObject } from "../kube-object"; +import type { StorageClassData } from "@k8slens/kube-object"; +import { StorageClass } from "@k8slens/kube-object"; import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api"; import { KubeApi } from "../kube-api"; -import type { KubeJsonApiData } from "../kube-json-api"; -import autoBind from "auto-bind"; -export interface TopologySelectorLabelRequirement { - key: string; - values: string[]; -} - -export interface TopologySelectorTerm { - matchLabelExpressions?: TopologySelectorLabelRequirement[]; -} - -export interface StorageClassData extends KubeJsonApiData, void, void> { - allowVolumeExpansion?: boolean; - allowedTopologies?: TopologySelectorTerm[]; - mountOptions?: string[]; - parameters?: Partial>; - provisioner: string; - reclaimPolicy?: string; - volumeBindingMode?: string; -} - -export class StorageClass extends KubeObject< - ClusterScopedMetadata, - void, - void -> { - static readonly kind = "StorageClass"; - static readonly namespaced = false; - static readonly apiBase = "/apis/storage.k8s.io/v1/storageclasses"; - - allowVolumeExpansion?: boolean; - allowedTopologies: TopologySelectorTerm[]; - mountOptions: string[]; - parameters: Partial>; - provisioner: string; - reclaimPolicy: string; - volumeBindingMode?: string; - - constructor({ - allowVolumeExpansion, - allowedTopologies = [], - mountOptions = [], - parameters = {}, - provisioner, - reclaimPolicy = "Delete", - volumeBindingMode, - ...rest - }: StorageClassData) { - super(rest); - autoBind(this); - this.allowVolumeExpansion = allowVolumeExpansion; - this.allowedTopologies = allowedTopologies; - this.mountOptions = mountOptions; - this.parameters = parameters; - this.provisioner = provisioner; - this.reclaimPolicy = reclaimPolicy; - this.volumeBindingMode = volumeBindingMode; - } - - isDefault() { - const annotations = this.metadata.annotations || {}; - - return ( - annotations["storageclass.kubernetes.io/is-default-class"] === "true" || - annotations["storageclass.beta.kubernetes.io/is-default-class"] === "true" - ); - } - - getVolumeBindingMode() { - return this.volumeBindingMode || "-"; - } - - getReclaimPolicy() { - return this.reclaimPolicy || "-"; - } -} export class StorageClassApi extends KubeApi { constructor(deps: KubeApiDependencies, opts: DerivedKubeApiOptions = {}) { diff --git a/packages/core/src/common/k8s-api/endpoints/types/security-context.ts b/packages/core/src/common/k8s-api/endpoints/types/security-context.ts deleted file mode 100644 index ce5cf60e40..0000000000 --- a/packages/core/src/common/k8s-api/endpoints/types/security-context.ts +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import type { Capabilities } from "./capabilities"; -import type { SeLinuxOptions } from "./se-linux-options"; -import type { SeccompProfile } from "./seccomp-profile"; -import type { WindowsSecurityContextOptions } from "./windows-security-context-options"; - -/** - * SecurityContext holds security configuration that will be applied to a container. Some fields are present in both SecurityContext and PodSecurityContext. When both are set, the values in SecurityContext take precedence. - */ -export interface SecurityContext { - /** - * AllowPrivilegeEscalation controls whether a process can gain more privileges than its parent process. This bool directly controls if the no_new_privs flag will be set on the container process. AllowPrivilegeEscalation is true always when the container is: 1) run as Privileged 2) has CAP_SYS_ADMIN - */ - allowPrivilegeEscalation?: boolean; - - capabilities?: Capabilities; - - /** - * Run container in privileged mode. Processes in privileged containers are essentially equivalent to root on the host. Defaults to false. - */ - privileged?: boolean; - - /** - * procMount denotes the type of proc mount to use for the containers. The default is DefaultProcMount which uses the container runtime defaults for readonly paths and masked paths. This requires the ProcMountType feature flag to be enabled. - */ - procMount?: string; - - /** - * Whether this container has a read-only root filesystem. Default is false. - */ - readOnlyRootFilesystem?: boolean; - - /** - * The GID to run the entrypoint of the container process. Uses runtime default if unset. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. - */ - runAsGroup?: number; - - /** - * Indicates that the container must run as a non-root user. If true, the Kubelet will validate the image at runtime to ensure that it does not run as UID 0 (root) and fail to start the container if it does. If unset or false, no such validation will be performed. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. - */ - runAsNonRoot?: boolean; - - /** - * The UID to run the entrypoint of the container process. Defaults to user specified in image metadata if unspecified. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. - */ - runAsUser?: number; - - seLinuxOptions?: SeLinuxOptions; - seccompProfile?: SeccompProfile; - windowsOptions?: WindowsSecurityContextOptions; -} diff --git a/packages/core/src/common/k8s-api/endpoints/validating-webhook-configuration.api.ts b/packages/core/src/common/k8s-api/endpoints/validating-webhook-configuration.api.ts index bb0f9d5117..38be6295fb 100644 --- a/packages/core/src/common/k8s-api/endpoints/validating-webhook-configuration.api.ts +++ b/packages/core/src/common/k8s-api/endpoints/validating-webhook-configuration.api.ts @@ -2,40 +2,9 @@ * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ -import type { NamespaceScopedMetadata, KubeObjectMetadata, KubeObjectScope } from "../kube-object"; -import { KubeObject } from "../kube-object"; +import { ValidatingWebhookConfiguration } from "@k8slens/kube-object"; import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api"; import { KubeApi } from "../kube-api"; -import type { KubeJsonApiData } from "../kube-json-api"; -import type { Webhook } from "./mutating-webhook-configuration.api"; - -export interface ValidatingWebhook extends Webhook { -} - -interface ValidatingWebhookConfigurationData extends KubeJsonApiData, void, void> { - webhooks?: ValidatingWebhook[]; -} - -export class ValidatingWebhookConfiguration extends KubeObject< - NamespaceScopedMetadata, - void, - void -> { - static kind = "ValidatingWebhookConfiguration"; - static namespaced = false; - static apiBase = "/apis/admissionregistration.k8s.io/v1/validatingwebhookconfigurations"; - - webhooks?: ValidatingWebhook[]; - - constructor({ webhooks, ...rest }: ValidatingWebhookConfigurationData) { - super(rest); - this.webhooks = webhooks; - } - - getWebhooks(): ValidatingWebhook[] { - return this.webhooks ?? []; - } -} export class ValidatingWebhookConfigurationApi extends KubeApi { constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) { diff --git a/packages/core/src/common/k8s-api/endpoints/vertical-pod-autoscaler.api.ts b/packages/core/src/common/k8s-api/endpoints/vertical-pod-autoscaler.api.ts index 4d8ebb43c1..c676f0010e 100644 --- a/packages/core/src/common/k8s-api/endpoints/vertical-pod-autoscaler.api.ts +++ b/packages/core/src/common/k8s-api/endpoints/vertical-pod-autoscaler.api.ts @@ -3,141 +3,10 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import type { BaseKubeObjectCondition, NamespaceScopedMetadata } from "../kube-object"; -import { KubeObject } from "../kube-object"; +import { VerticalPodAutoscaler } from "@k8slens/kube-object"; import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api"; import { KubeApi } from "../kube-api"; -import type { CrossVersionObjectReference } from "./types/cross-version-object-reference"; -export enum ResourceName { - ResourceCPU = "cpu", - ResourceMemory = "memory", - ResourceStorage = "storage", -} - -export type ResourceList = Partial>; - -export interface RecommendedContainerResources { - containerName?: string; - target: ResourceList; - lowerBound?: ResourceList; - upperBound?: ResourceList; - uncappedTarget?: ResourceList; -} -export interface RecommendedPodResources { - containerRecommendations?: RecommendedContainerResources[]; -} - -export interface VerticalPodAutoscalerStatus { - conditions?: BaseKubeObjectCondition[]; - recommendation?: RecommendedPodResources; -} - -export interface VerticalPodAutoscalerRecommenderSelector { - name: string; -} - -export enum ContainerScalingMode { - ContainerScalingModeAuto = "Auto", - ContainerScalingModeOff = "Off", -} - -export enum ControlledValues { - ControlledValueRequestsAndLimits = "RequestsAndLimits", - ControlledValueRequestsOnly = "RequestsOnly", -} - -/** - * ContainerResourcePolicy controls how autoscaler computes the recommended resources for - * a specific container. - */ -export interface ContainerResourcePolicy { - containerName?: string; - mode?: ContainerScalingMode; - minAllowed?: ResourceList; - maxAllowed?: ResourceList; - controlledResources?: ResourceName[]; - controlledValues?: ControlledValues; -} - -/** - * Controls how the autoscaler computes recommended resources. - * The resource policy may be used to set constraints on the recommendations for individual - * containers. - * If not specified, the autoscaler computes recommended resources for all containers in the - * pod, without additional constraints. - */ -export interface PodResourcePolicy { - containerPolicies?: ContainerResourcePolicy[]; // Per-container resource policies. -} - -export enum UpdateMode { - /** - * UpdateModeOff means that autoscaler never changes Pod resources. - * The recommender still sets the recommended resources in the - * VerticalPodAutoscaler object. This can be used for a "dry run". - */ - UpdateModeOff = "Off", - /** - * UpdateModeInitial means that autoscaler only assigns resources on pod - * creation and does not change them during the lifetime of the pod. - */ - UpdateModeInitial = "Initial", - /** - * UpdateModeRecreate means that autoscaler assigns resources on pod - * creation and additionally can update them during the lifetime of the - * pod by deleting and recreating the pod. - */ - UpdateModeRecreate = "Recreate", - /** - * UpdateModeAuto means that autoscaler assigns resources on pod creation - * and additionally can update them during the lifetime of the pod, - * using any available update method. Currently this is equivalent to - * Recreate, which is the only available update method. - */ - UpdateModeAuto = "Auto", -} -export interface PodUpdatePolicy { - minReplicas?: number; - updateMode?: UpdateMode; -} - -export interface VerticalPodAutoscalerSpec { - targetRef: CrossVersionObjectReference; - updatePolicy?: PodUpdatePolicy; - resourcePolicy?: PodResourcePolicy; - recommenders?: VerticalPodAutoscalerRecommenderSelector[]; -} - -export class VerticalPodAutoscaler extends KubeObject< - NamespaceScopedMetadata, - VerticalPodAutoscalerStatus, - VerticalPodAutoscalerSpec -> { - static readonly kind = "VerticalPodAutoscaler"; - static readonly namespaced = true; - static readonly apiBase = "/apis/autoscaling.k8s.io/v1/verticalpodautoscalers"; - - getReadyConditions() { - return this.getConditions().filter(({ isReady }) => isReady); - } - - getConditions() { - return this.status?.conditions?.map(condition => { - const { message, reason, lastTransitionTime, status } = condition; - - return { - ...condition, - isReady: status === "True", - tooltip: `${message || reason || ""} (${lastTransitionTime})`, - }; - }) ?? []; - } - - getMode() { - return this.spec.updatePolicy?.updateMode ?? UpdateMode.UpdateModeAuto; - } -} export class VerticalPodAutoscalerApi extends KubeApi { constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) { diff --git a/packages/core/src/common/k8s-api/json-api.ts b/packages/core/src/common/k8s-api/json-api.ts index 8221a20129..9b24d9cc1c 100644 --- a/packages/core/src/common/k8s-api/json-api.ts +++ b/packages/core/src/common/k8s-api/json-api.ts @@ -249,12 +249,14 @@ export class JsonApi = Js return []; } - if (Array.isArray(error.errors)) { - return error.errors.map(error => error.title); + const { errors, message } = error as { errors?: { title: string }[]; message?: string }; + + if (Array.isArray(errors)) { + return errors.map(error => error.title); } - if (isString(error.message)) { - return [error.message]; + if (isString(message)) { + return [message]; } return [res.statusText || "Error!"]; diff --git a/packages/core/src/common/k8s-api/kube-api.ts b/packages/core/src/common/k8s-api/kube-api.ts index 4bdbd02e45..585f845b49 100644 --- a/packages/core/src/common/k8s-api/kube-api.ts +++ b/packages/core/src/common/k8s-api/kube-api.ts @@ -8,11 +8,11 @@ import { merge } from "lodash"; import { stringify } from "querystring"; import { createKubeApiURL, parseKubeApi } from "./kube-api-parse"; -import type { KubeObjectConstructor, KubeJsonApiDataFor, KubeObjectMetadata } from "./kube-object"; -import { KubeObject, KubeStatus, isKubeStatusData } from "./kube-object"; +import type { KubeObjectConstructor, KubeJsonApiDataFor, KubeObjectMetadata, KubeJsonApiData, KubeObject, KubeObjectScope } from "@k8slens/kube-object"; +import { isJsonApiData, isJsonApiDataList, isPartialJsonApiData, KubeStatus, isKubeStatusData } from "@k8slens/kube-object"; import byline from "byline"; import type { IKubeWatchEvent } from "./kube-watch-event"; -import type { KubeJsonApiData, KubeJsonApi } from "./kube-json-api"; +import type { KubeJsonApi } from "./kube-json-api"; import type { Disposer } from "@k8slens/utilities"; import { isDefined, noop, WrappedAbortController } from "@k8slens/utilities"; import type { RequestInit, Response } from "@k8slens/node-fetch"; @@ -157,7 +157,7 @@ const getOrderedVersions = (list: KubeApiResourceVersionList, allowedUsableVersi export type PropagationPolicy = undefined | "Orphan" | "Foreground" | "Background"; -export type KubeApiWatchCallback = (data: IKubeWatchEvent | null, error: KubeStatus | Response | null | any) => void; +export type KubeApiWatchCallback = (data: IKubeWatchEvent | null, error: KubeStatus | Response | null | Record) => void; export interface KubeApiWatchOptions> { /** @@ -213,6 +213,29 @@ export interface ResourceDescriptor { namespace?: string; } +export type SpecificResourceDescriptor = { + /** + * The name of the kubernetes resource + */ + name: string; +} & ( + Scope extends KubeObjectScope.Cluster + ? {} + : Scope extends KubeObjectScope.Namespace + ? { + /** + * The namespace that the resource lives in + */ + namespace: string; + } + : { + namespace?: string; + } +); + +export type NamespacedResourceDescriptor = SpecificResourceDescriptor; +export type ClusterScopedResourceDescriptor = SpecificResourceDescriptor; + export interface DeleteResourceDescriptor extends ResourceDescriptor { /** * This determinines how child resources should be handled by kubernetes @@ -441,7 +464,7 @@ export class KubeApi< const KubeObjectConstructor = this.objectConstructor; // process items list response, check before single item since there is overlap - if (KubeObject.isJsonApiDataList(data, KubeObject.isPartialJsonApiData)) { + if (isJsonApiDataList(data, isPartialJsonApiData)) { const { apiVersion, items, metadata } = data; this.setResourceVersion(namespace, metadata.resourceVersion); @@ -467,22 +490,21 @@ export class KubeApi< } // process a single item - if (KubeObject.isJsonApiData(data)) { + if (isJsonApiData(data)) { this.ensureMetadataSelfLink(data.metadata); - return new KubeObjectConstructor(data as never); + return new KubeObjectConstructor(data as Data); + } + + if (!Array.isArray(data)) { + return null; } // custom apis might return array for list response, e.g. users, groups, etc. - if (Array.isArray(data)) { - return data.map(data => { - this.ensureMetadataSelfLink(data.metadata); - - return new KubeObjectConstructor(data); - }); - } - - return null; + return data + .filter(isJsonApiData) + .map((data) => (this.ensureMetadataSelfLink(data.metadata), data)) + .map((data) => new KubeObjectConstructor(data as Data)); } private ensureMetadataSelfLink(metadata: T): asserts metadata is T & { selfLink: string } { @@ -595,7 +617,7 @@ export class KubeApi< /** * Some k8s resources might implement special "delete" (e.g. pod.api) * See also: https://kubernetes.io/docs/concepts/scheduling-eviction/api-eviction/ - * By default should work same as KubeObject.remove() + * By default should work same as delete() */ async evict(desc: DeleteResourceDescriptor): Promise { return this.delete(desc); @@ -714,7 +736,7 @@ export class KubeApi< }); } - byline(response.body).on("data", (line) => { + byline(response.body).on("data", (line: string) => { try { const event = JSON.parse(line) as IKubeWatchEvent; @@ -731,11 +753,11 @@ export class KubeApi< } }); }) - .catch(error => { + .catch((error: unknown) => { if (!abortController.signal.aborted) { this.dependencies.logger.error(`[KUBE-API] watch (${watchId}) threw ${watchUrl}`, error); } - callback(null, error); + callback(null, error as Record); }); return () => { diff --git a/packages/core/src/common/k8s-api/kube-json-api.ts b/packages/core/src/common/k8s-api/kube-json-api.ts index 1a88495dfb..5b8188451e 100644 --- a/packages/core/src/common/k8s-api/kube-json-api.ts +++ b/packages/core/src/common/k8s-api/kube-json-api.ts @@ -3,35 +3,10 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import type { JsonApiData, JsonApiError } from "./json-api"; +import type { JsonApiError } from "./json-api"; import { JsonApi } from "./json-api"; import type { Response } from "@k8slens/node-fetch"; -import type { KubeJsonApiObjectMetadata } from "./kube-object"; - -export interface KubeJsonApiListMetadata { - resourceVersion: string; - selfLink?: string; -} - -export interface KubeJsonApiDataList { - kind: string; - apiVersion: string; - items: T[]; - metadata: KubeJsonApiListMetadata; -} - -export interface KubeJsonApiData< - Metadata extends KubeJsonApiObjectMetadata = KubeJsonApiObjectMetadata, - Status = unknown, - Spec = unknown, -> extends JsonApiData { - kind: string; - apiVersion: string; - metadata: Metadata; - status?: Status; - spec?: Spec; - [otherKeys: string]: unknown; -} +import type { KubeJsonApiData } from "@k8slens/kube-object"; export interface KubeJsonApiError extends JsonApiError { code: number; diff --git a/packages/core/src/common/k8s-api/kube-object.store.ts b/packages/core/src/common/k8s-api/kube-object.store.ts index 9efaca4dee..8be24ab5f5 100644 --- a/packages/core/src/common/k8s-api/kube-object.store.ts +++ b/packages/core/src/common/k8s-api/kube-object.store.ts @@ -6,8 +6,8 @@ import { action, computed, makeObservable, observable, reaction } from "mobx"; import type { Disposer } from "@k8slens/utilities"; import { waitUntilDefined, includes, rejectPromiseBy, object } from "@k8slens/utilities"; -import type { KubeJsonApiDataFor, KubeObject } from "./kube-object"; -import { KubeStatus } from "./kube-object"; +import type { KubeJsonApiDataFor, KubeObject } from "@k8slens/kube-object"; +import { KubeStatus } from "@k8slens/kube-object"; import type { IKubeWatchEvent } from "./kube-watch-event"; import { ItemStore } from "../item.store"; import type { KubeApiQueryParams, KubeApi, KubeApiWatchCallback } from "./kube-api"; @@ -398,18 +398,18 @@ export class KubeObjectStore< } async removeSelectedItems() { - await Promise.all(this.selectedItems.map(this.remove)); + await Promise.all(this.selectedItems.map(item => this.remove(item))); } async removeItems(items: K[]) { - await Promise.all(items.map(this.remove)); + await Promise.all(items.map(item => this.remove(item))); } // collect items from watch-api events to avoid UI blowing up with huge streams of data protected readonly eventsBuffer = observable.array>([], { deep: false }); protected bindWatchEventsUpdater(delay = 1000) { - reaction(() => [...this.eventsBuffer], this.updateFromEventsBuffer, { + reaction(() => [...this.eventsBuffer], () => this.updateFromEventsBuffer(), { delay, }); } @@ -456,7 +456,7 @@ export class KubeObjectStore< const signal = abortController.signal; const callback: KubeApiWatchCallback = (data, error) => { - if (!this.isLoaded || error?.type === "aborted") return; + if (!this.isLoaded || (error as Record | null)?.type === "aborted") return; if (error instanceof Response) { if (error.status === 404 || error.status === 401) { @@ -471,7 +471,7 @@ export class KubeObjectStore< clearTimeout(timedRetry); // resourceVersion has gone, let's try to reload timedRetry = setTimeout(() => { - ( + void ( namespace ? this.loadAll({ namespaces: [namespace], reqInit: { signal }, ...opts }) : this.loadAll({ merge: false, reqInit: { signal }, ...opts }) diff --git a/packages/core/src/common/k8s-api/kube-object.ts b/packages/core/src/common/k8s-api/kube-object.ts deleted file mode 100644 index aff052c3d5..0000000000 --- a/packages/core/src/common/k8s-api/kube-object.ts +++ /dev/null @@ -1,743 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -// Base class for all kubernetes objects - -import moment from "moment"; -import type { - KubeJsonApiData, - KubeJsonApiDataList, - KubeJsonApiListMetadata, -} from "./kube-json-api"; -import { - formatDuration, - hasOptionalTypedProperty, - hasTypedProperty, - isObject, - isString, - isNumber, - bindPredicate, - isTypedArray, - isRecord, -} from "@k8slens/utilities"; -import type { ItemObject } from "@k8slens/list-layout"; -import type { Patch } from "rfc6902"; -import assert from "assert"; -import type { JsonObject } from "type-fest"; -import requestKubeObjectPatchInjectable from "./endpoints/resource-applier.api/request-patch.injectable"; -import { apiKubeInjectionToken } from "./api-kube"; -import requestKubeObjectCreationInjectable from "./endpoints/resource-applier.api/request-update.injectable"; -import { dump } from "js-yaml"; -import { getLegacyGlobalDiForExtensionApi } from "@k8slens/legacy-global-di"; -import autoBind from "auto-bind"; - -export type KubeJsonApiDataFor = K extends KubeObject - ? KubeJsonApiData - : never; - -export interface KubeObjectConstructorData { - readonly kind?: string; - readonly namespaced?: boolean; - readonly apiBase?: string; -} - -export type KubeObjectConstructor = (new (data: Data) => K) & KubeObjectConstructorData; - -export interface OwnerReference { - apiVersion: string; - kind: string; - name: string; - uid: string; - controller?: boolean; - blockOwnerDeletion?: boolean; -} - -export type KubeTemplateObjectMetadata = Pick, "annotations" | "finalizers" | "generateName" | "labels" | "ownerReferences"> & { - name?: string; - namespace?: ScopedNamespace; -}; - -export interface BaseKubeJsonApiObjectMetadata { - /** - * Annotations is an unstructured key value map stored with a resource that may be set by - * external tools to store and retrieve arbitrary metadata. They are not queryable and should be - * preserved when modifying objects. - * - * More info: http://kubernetes.io/docs/user-guide/annotations - */ - annotations?: Partial>; - - /** - * The name of the cluster which the object belongs to. This is used to distinguish resources - * with same name and namespace in different clusters. This field is not set anywhere right now - * and apiserver is going to ignore it if set in create or update request. - */ - clusterName?: string; - - /** - * CreationTimestamp is a timestamp representing the server time when this object was created. It - * is not guaranteed to be set in happens-before order across separate operations. Clients may - * not set this value. It is represented in RFC3339 form and is in UTC. Populated by the system. - * - * More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata - */ - readonly creationTimestamp?: string; - - /** - * Number of seconds allowed for this object to gracefully terminate before it will be removed - * from the system. Only set when deletionTimestamp is also set. May only be shortened. - */ - readonly deletionGracePeriodSeconds?: number; - - /** - * DeletionTimestamp is RFC 3339 date and time at which this resource will be deleted. This field - * is set by the server when a graceful deletion is requested by the user, and is not directly - * settable by a client. The resource is expected to be deleted (no longer visible from resource - * lists, and not reachable by name) after the time in this field, once the finalizers list is - * empty. As long as the finalizers list contains items, deletion is blocked. Once the - * `deletionTimestamp` is set, this value may not be unset or be set further into the future, - * although it may be shortened or the resource may be deleted prior to this time. For example, - * a user may request that a pod is deleted in 30 seconds. The Kubelet will react by sending a - * graceful termination signal to the containers in the pod. After that 30 seconds, the Kubelet - * will send a hard termination signal (SIGKILL) to the container and after cleanup, remove the - * pod from the API. In the presence of network partitions, this object may still exist after - * this timestamp, until an administrator or automated process can determine the resource is - * fully terminated. If not set, graceful deletion of the object has not been requested. - * Populated by the system when a graceful deletion is requested. - * - * More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata - */ - readonly deletionTimestamp?: string; - - /** - * Must be empty before the object is deleted from the registry. Each entry is an identifier for - * the responsible component that will remove the entry from the list. If the deletionTimestamp - * of the object is non-nil, entries in this list can only be removed. Finalizers may be - * processed and removed in any order. Order is NOT enforced because it introduces significant - * risk of stuck finalizers. finalizers is a shared field, any actor with permission can reorder - * it. If the finalizer list is processed in order, then this can lead to a situation in which - * the component responsible for the first finalizer in the list is waiting for a signal (field - * value, external system, or other) produced by a component responsible for a finalizer later in - * the list, resulting in a deadlock. Without enforced ordering finalizers are free to order - * amongst themselves and are not vulnerable to ordering changes in the list. - */ - finalizers?: string[]; - - /** - * GenerateName is an optional prefix, used by the server, to generate a unique name ONLY IF the - * Name field has not been provided. If this field is used, the name returned to the client will - * be different than the name passed. This value will also be combined with a unique suffix. The - * provided value has the same validation rules as the Name field, and may be truncated by the - * length of the suffix required to make the value unique on the server. If this field is - * specified and the generated name exists, the server will NOT return a 409 - instead, it will - * either return 201 Created or 500 with Reason ServerTimeout indicating a unique name could not - * be found in the time allotted, and the client should retry (optionally after the time indicated - * in the Retry-After header). Applied only if Name is not specified. - * - * More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#idempotency - */ - generateName?: string; - - /** - * A sequence number representing a specific generation of the desired state. Populated by the - * system. - */ - readonly generation?: number; - - /** - * Map of string keys and values that can be used to organize and categorize (scope and select) - * objects. May match selectors of replication controllers and services. - * - * More info: http://kubernetes.io/docs/user-guide/labels - */ - labels?: Partial>; - - /** - * ManagedFields maps workflow-id and version to the set of fields that are managed by that - * workflow. This is mostly for internal housekeeping, and users typically shouldn't need to set - * or understand this field. A workflow can be the user's name, a controller's name, or the name - * of a specific apply path like "ci-cd". The set of fields is always in the version that the - * workflow used when modifying the object. - */ - managedFields?: unknown[]; - - /** - * Name must be unique within a namespace. Is required when creating resources, although some - * resources may allow a client to request the generation of an appropriate name automatically. - * Name is primarily intended for creation idempotence and configuration definition. - * - * More info: http://kubernetes.io/docs/user-guide/identifiers#names - */ - readonly name: string; - - /** - * Namespace defines the space within which each name must be unique. An empty namespace is - * equivalent to the "default" namespace, but "default" is the canonical representation. Not all - * objects are required to be scoped to a namespace - the value of this field for those objects - * will be empty. Must be a DNS_LABEL. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/namespaces - */ - readonly namespace?: ScopedNamespace; - - /** - * List of objects depended by this object. If ALL objects in the list have been deleted, this - * object will be garbage collected. If this object is managed by a controller, then an entry in - * this list will point to this controller, with the controller field set to true. There cannot - * be more than one managing controller. - */ - ownerReferences?: OwnerReference[]; - - /** - * An opaque value that represents the internal version of this object that can be used by - * clients to determine when objects have changed. May be used for optimistic concurrency, change - * detection, and the watch operation on a resource or set of resources. Clients must treat these - * values as opaque and passed unmodified back to the server. They may only be valid for a - * particular resource or set of resources. Populated by the system. Value must be treated as - * opaque by clients. - * - * More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency - */ - readonly resourceVersion?: string; - - /** - * SelfLink is a URL representing this object. Populated by the system. - */ - readonly selfLink?: string; - - /** - * UID is the unique in time and space value for this object. It is typically generated by the - * server on successful creation of a resource and is not allowed to change on PUT operations. - * Populated by the system. - * - * More info: http://kubernetes.io/docs/user-guide/identifiers#uids - */ - readonly uid?: string; - - [key: string]: unknown; -} - -export type KubeJsonApiObjectMetadata = - BaseKubeJsonApiObjectMetadata - & ( - Namespaced extends KubeObjectScope.Namespace - ? { readonly namespace: string } - : {} - ); - -export type KubeObjectMetadata = - KubeJsonApiObjectMetadata - & { - readonly selfLink: string; - readonly uid: string; - readonly name: string; - readonly resourceVersion: string; -}; - -export type NamespaceScopedMetadata = KubeObjectMetadata; -export type ClusterScopedMetadata = KubeObjectMetadata; - -export interface KubeStatusData { - kind: string; - apiVersion: string; - code: number; - message?: string; - reason?: string; - status?: string; -} - -export interface EvictionObject { - kind: "Eviction"; - apiVersion: string | "policy/v1"; - metadata: Partial; - deleteOptions?: { - kind?: string; - apiVersion?: string; - dryRun?: string[]; - gracePeriodSeconds?: number; - orphanDependents?: boolean; - propagationPolicy?: string; - preconditions?: { - resourceVersion: string; - uid: string; - }[]; - }; -} - -export function isKubeStatusData(object: unknown): object is KubeStatusData { - return isObject(object) - && hasTypedProperty(object, "kind", isString) - && hasTypedProperty(object, "apiVersion", isString) - && hasTypedProperty(object, "code", isNumber) - && ( - hasOptionalTypedProperty(object, "message", isString) - || hasOptionalTypedProperty(object, "reason", isString) - || hasOptionalTypedProperty(object, "status", isString) - ) - && object.kind === "Status"; -} - -export class KubeStatus { - public readonly kind = "Status"; - public readonly apiVersion: string; - public readonly code: number; - public readonly message?: string; - public readonly reason?: string; - public readonly status?: string; - - constructor(data: KubeStatusData) { - this.apiVersion = data.apiVersion; - this.code = data.code; - this.message = data.message || ""; - this.reason = data.reason || ""; - this.status = data.status || ""; - } - - getExplanation(): string { - const { code, message, reason, status } = this; - - return `${code}: ${message || reason || status}`; - } -} - -export interface BaseKubeObjectCondition { - /** - * Last time the condition transit from one status to another. - * - * @type Date - */ - lastTransitionTime?: string; - /** - * A human readable message indicating details about last transition. - */ - message?: string; - /** - * brief (usually one word) readon for the condition's last transition. - */ - reason?: string; - /** - * Status of the condition - */ - status: "True" | "False" | "Unknown"; - /** - * Type of the condition - */ - type: string; -} - -export interface KubeObjectStatus { - conditions?: BaseKubeObjectCondition[]; -} - -export type KubeMetaField = keyof KubeJsonApiObjectMetadata; - -export class KubeCreationError extends Error { - constructor(message: string, public data: any) { - super(message); - } -} - -export type LabelMatchExpression = { - /** - * The label key that the selector applies to. - */ - key: string; -} & ( - { - /** - * This represents the key's relationship to a set of values. - */ - operator: "Exists" | "DoesNotExist"; - values?: undefined; - } - | - { - operator: "In" | "NotIn"; - /** - * The set of values for to match according to the operator for the label. - */ - values: string[]; - } -); - -export interface Toleration { - key?: string; - operator?: string; - effect?: string; - value?: string; - tolerationSeconds?: number; -} - -export interface ObjectReference { - apiVersion?: string; - fieldPath?: string; - kind?: string; - name: string; - namespace?: string; - resourceVersion?: string; - uid?: string; -} - -export interface LocalObjectReference { - name: string; -} - -export interface TypedLocalObjectReference { - apiGroup?: string; - kind: string; - name: string; -} - -export interface NodeAffinity { - nodeSelectorTerms?: LabelSelector[]; - weight: number; - preference: LabelSelector; -} - -export interface PodAffinity { - labelSelector: LabelSelector; - topologyKey: string; -} - -export interface SpecificAffinity { - requiredDuringSchedulingIgnoredDuringExecution?: T[]; - preferredDuringSchedulingIgnoredDuringExecution?: T[]; -} - -export interface Affinity { - nodeAffinity?: SpecificAffinity; - podAffinity?: SpecificAffinity; - podAntiAffinity?: SpecificAffinity; -} - -export interface LabelSelector { - matchLabels?: Partial>; - matchExpressions?: LabelMatchExpression[]; -} - -export enum KubeObjectScope { - Namespace, - Cluster, -} -export type ScopedNamespace = ( - Namespaced extends KubeObjectScope.Namespace - ? string - : Namespaced extends KubeObjectScope.Cluster - ? undefined - : string | undefined -); - -const resourceApplierAnnotationsForFiltering = [ - "kubectl.kubernetes.io/last-applied-configuration", -]; - -const filterOutResourceApplierAnnotations = (label: string) => !resourceApplierAnnotationsForFiltering.some(key => label.startsWith(key)); - -export interface RawKubeObject< - Metadata extends KubeObjectMetadata = KubeObjectMetadata, - Status = Record, - Spec = Record, -> { - apiVersion: string; - kind: string; - metadata: Metadata; - status?: Status; - spec?: Spec; - [otherFields: string]: unknown; -} - -export class KubeObject< - Metadata extends KubeObjectMetadata = KubeObjectMetadata, - Status = unknown, - Spec = unknown, -> implements ItemObject { - static readonly kind?: string; - static readonly namespaced?: boolean; - static readonly apiBase?: string; - - apiVersion!: string; - kind!: string; - metadata!: Metadata; - status?: Status; - spec!: Spec; - - static create< - Metadata extends KubeObjectMetadata = KubeObjectMetadata, - Status = unknown, - Spec = unknown, - >(data: KubeJsonApiData) { - return new KubeObject(data); - } - - static isNonSystem(item: KubeJsonApiData | KubeObject, unknown, unknown>) { - return !item.metadata.name?.startsWith("system:"); - } - - static isJsonApiData(object: unknown): object is KubeJsonApiData { - return ( - isObject(object) - && hasTypedProperty(object, "kind", isString) - && hasTypedProperty(object, "apiVersion", isString) - && hasTypedProperty(object, "metadata", KubeObject.isKubeJsonApiMetadata) - ); - } - - static isKubeJsonApiListMetadata(object: unknown): object is KubeJsonApiListMetadata { - return ( - isObject(object) - && hasOptionalTypedProperty(object, "resourceVersion", isString) - && hasOptionalTypedProperty(object, "selfLink", isString) - ); - } - - static isKubeJsonApiMetadata(object: unknown): object is KubeJsonApiObjectMetadata { - return ( - isObject(object) - && hasTypedProperty(object, "uid", isString) - && hasTypedProperty(object, "name", isString) - && hasTypedProperty(object, "resourceVersion", isString) - && hasOptionalTypedProperty(object, "selfLink", isString) - && hasOptionalTypedProperty(object, "namespace", isString) - && hasOptionalTypedProperty(object, "creationTimestamp", isString) - && hasOptionalTypedProperty(object, "continue", isString) - && hasOptionalTypedProperty(object, "finalizers", bindPredicate(isTypedArray, isString)) - && hasOptionalTypedProperty(object, "labels", bindPredicate(isRecord, isString, isString)) - && hasOptionalTypedProperty(object, "annotations", bindPredicate(isRecord, isString, isString)) - ); - } - - static isPartialJsonApiMetadata(object: unknown): object is Partial { - return ( - isObject(object) - && hasOptionalTypedProperty(object, "uid", isString) - && hasOptionalTypedProperty(object, "name", isString) - && hasOptionalTypedProperty(object, "resourceVersion", isString) - && hasOptionalTypedProperty(object, "selfLink", isString) - && hasOptionalTypedProperty(object, "namespace", isString) - && hasOptionalTypedProperty(object, "creationTimestamp", isString) - && hasOptionalTypedProperty(object, "continue", isString) - && hasOptionalTypedProperty(object, "finalizers", bindPredicate(isTypedArray, isString)) - && hasOptionalTypedProperty(object, "labels", bindPredicate(isRecord, isString, isString)) - && hasOptionalTypedProperty(object, "annotations", bindPredicate(isRecord, isString, isString)) - ); - } - - static isPartialJsonApiData(object: unknown): object is Partial { - return ( - isObject(object) - && hasOptionalTypedProperty(object, "kind", isString) - && hasOptionalTypedProperty(object, "apiVersion", isString) - && hasOptionalTypedProperty(object, "metadata", KubeObject.isPartialJsonApiMetadata) - ); - } - - static isJsonApiDataList(object: unknown, verifyItem: (val: unknown) => val is T): object is KubeJsonApiDataList { - return ( - isObject(object) - && hasTypedProperty(object, "kind", isString) - && hasTypedProperty(object, "apiVersion", isString) - && hasTypedProperty(object, "metadata", KubeObject.isKubeJsonApiListMetadata) - && hasTypedProperty(object, "items", bindPredicate(isTypedArray, verifyItem)) - ); - } - - static stringifyLabels(labels?: Partial>): string[] { - if (!labels) return []; - - return Object.entries(labels).map(([name, value]) => `${name}=${value}`); - } - - constructor(data: KubeJsonApiData) { - if (!isObject(data)) { - throw new TypeError(`Cannot create a KubeObject from ${typeof data}`); - } - - if (!isObject(data.metadata)) { - throw new KubeCreationError(`Cannot create a KubeObject from an object without metadata`, data); - } - - if (!isString(data.metadata.name)) { - throw new KubeCreationError(`Cannot create a KubeObject from an object without metadata.name being a string`, data); - } - - if (!isString(data.metadata.uid)) { - throw new KubeCreationError(`Cannot create a KubeObject from an object without metadata.uid being a string`, data); - } - - if (!isString(data.metadata.resourceVersion)) { - throw new KubeCreationError(`Cannot create a KubeObject from an object without metadata.resourceVersion being a string`, data); - } - - if (!isString(data.metadata.selfLink)) { - throw new KubeCreationError(`Cannot create a KubeObject from an object without metadata.selfLink being a string`, data); - } - - Object.assign(this, data); - autoBind(this); - } - - get selfLink() { - return this.metadata.selfLink; - } - - getId() { - return this.metadata.uid; - } - - getResourceVersion() { - return this.metadata.resourceVersion; - } - - getScopedName() { - const ns = this.getNs(); - const res = ns ? `${ns}/` : ""; - - return res + this.getName(); - } - - getName() { - return this.metadata.name; - } - - getNs(): Metadata["namespace"] { - // avoid "null" serialization via JSON.stringify when post data - return (this.metadata.namespace || undefined) as never; - } - - /** - * This function computes the number of milliseconds from the UNIX EPOCH to the - * creation timestamp of this object. - */ - getCreationTimestamp() { - if (!this.metadata.creationTimestamp) { - return Date.now(); - } - - return new Date(this.metadata.creationTimestamp).getTime(); - } - - /** - * @deprecated This function computes a new "now" on every call which might cause subtle issues if called multiple times - * - * NOTE: Generally you can use `getCreationTimestamp` instead. - */ - getTimeDiffFromNow(): number { - if (!this.metadata.creationTimestamp) { - return 0; - } - - return Date.now() - new Date(this.metadata.creationTimestamp).getTime(); - } - - /** - * @deprecated This function computes a new "now" on every call might cause subtle issues if called multiple times - * - * NOTE: this function also is not reactive to updates in the current time so it should not be used for renderering - */ - getAge(humanize = true, compact = true, fromNow = false): string | number { - if (fromNow) { - return moment(this.metadata.creationTimestamp).fromNow(); // "string", getTimeDiffFromNow() cannot be used - } - const diff = this.getTimeDiffFromNow(); - - if (humanize) { - return formatDuration(diff, compact); - } - - return diff; - } - - getFinalizers(): string[] { - return this.metadata.finalizers || []; - } - - getLabels(): string[] { - return KubeObject.stringifyLabels(this.metadata.labels); - } - - getAnnotations(filter = false): string[] { - const labels = KubeObject.stringifyLabels(this.metadata.annotations); - - if (!filter) { - return labels; - } - - return labels.filter(filterOutResourceApplierAnnotations); - } - - getOwnerRefs() { - const refs = this.metadata.ownerReferences || []; - const namespace = this.getNs(); - - return refs.map(ownerRef => ({ ...ownerRef, namespace })); - } - - getSearchFields() { - const { getName, getId, getNs, getAnnotations, getLabels } = this; - - return [ - getName(), - getNs(), - getId(), - ...getLabels(), - ...getAnnotations(true), - ]; - } - - toPlainObject() { - return JSON.parse(JSON.stringify(this)) as JsonObject; - } - - /** - * @deprecated use KubeApi.patch instead - */ - async patch(patch: Patch): Promise { - const di = getLegacyGlobalDiForExtensionApi(); - const requestKubeObjectPatch = di.inject(requestKubeObjectPatchInjectable); - const result = await requestKubeObjectPatch(this.getName(), this.kind, this.getNs(), patch); - - if (!result.callWasSuccessful) { - throw new Error(result.error); - } - - return result.response; - } - - /** - * Perform a full update (or more specifically a replace) - * - * Note: this is brittle if `data` is not actually partial (but instead whole). - * As fields such as `resourceVersion` will probably out of date. This is a - * common race condition. - * - * @deprecated use KubeApi.update instead - */ - async update(data: Partial): Promise { - const di = getLegacyGlobalDiForExtensionApi(); - const requestKubeObjectCreation = di.inject(requestKubeObjectCreationInjectable); - const descriptor = dump({ - ...this.toPlainObject(), - ...data, - }); - - const result = await requestKubeObjectCreation(descriptor); - - if (!result.callWasSuccessful) { - throw new Error(result.error); - } - - return result.response; - } - - /** - * @deprecated use KubeApi.delete instead - */ - delete(params?: object) { - assert(this.selfLink, "selfLink must be present to delete self"); - - const di = getLegacyGlobalDiForExtensionApi(); - const apiKube = di.inject(apiKubeInjectionToken); - - return apiKube.del(this.selfLink, params); - } -} diff --git a/packages/core/src/common/k8s-api/kube-watch-event.ts b/packages/core/src/common/k8s-api/kube-watch-event.ts index 0f42bf4054..bcef6c234f 100644 --- a/packages/core/src/common/k8s-api/kube-watch-event.ts +++ b/packages/core/src/common/k8s-api/kube-watch-event.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import type { KubeStatusData } from "./kube-object"; +import type { KubeStatusData } from "@k8slens/kube-object"; export type IKubeWatchEvent = { readonly type: "ADDED" | "MODIFIED" | "DELETED"; diff --git a/packages/core/src/extensions/common-api/k8s-api.ts b/packages/core/src/extensions/common-api/k8s-api.ts index d2d640a6d3..d579e0a86e 100644 --- a/packages/core/src/extensions/common-api/k8s-api.ts +++ b/packages/core/src/extensions/common-api/k8s-api.ts @@ -16,7 +16,7 @@ import { asLegacyGlobalFunctionForExtensionApi, asLegacyGlobalForExtensionApi, g import type { KubernetesCluster } from "./catalog"; import type { KubeApiDataFrom, KubeObjectStoreOptions } from "../../common/k8s-api/kube-object.store"; import { KubeObjectStore as InternalKubeObjectStore } from "../../common/k8s-api/kube-object.store"; -import type { KubeJsonApiDataFor, KubeObject } from "../../common/k8s-api/kube-object"; +import type { KubeJsonApiDataFor, KubeObject } from "@k8slens/kube-object"; import type { DerivedKubeApiOptions, KubeApiDependencies, KubeApiOptions } from "../../common/k8s-api/kube-api"; import { KubeApi as InternalKubeApi } from "../../common/k8s-api/kube-api"; import clusterFrameContextForNamespacedResourcesInjectable from "../../renderer/cluster-frame-context/for-namespaced-resources.injectable"; @@ -120,7 +120,17 @@ export type { CreateKubeApiForLocalClusterConfig as ILocalKubeApiConfig } from " export { KubeObject, KubeStatus, -} from "../../common/k8s-api/kube-object"; + isJsonApiData, + isJsonApiDataList, + isKubeJsonApiListMetadata, + isKubeJsonApiMetadata, + isKubeObjectNonSystem, + isKubeStatusData, + isPartialJsonApiData, + isPartialJsonApiMetadata, + createKubeObject, + stringifyLabels, +} from "@k8slens/kube-object"; export type { OwnerReference, KubeObjectMetadata, @@ -130,11 +140,8 @@ export type { KubeJsonApiObjectMetadata, KubeStatusData, KubeJsonApiDataFor, -} from "../../common/k8s-api/kube-object"; - -export type { KubeJsonApiData, -} from "../../common/k8s-api/kube-json-api"; +} from "@k8slens/kube-object"; function KubeJsonApiCstr(config: JsonApiConfig, reqInit?: RequestInit) { const di = getLegacyGlobalDiForExtensionApi(); @@ -291,4 +298,4 @@ export { ClusterRole, ClusterRoleBinding, CustomResourceDefinition, -} from "../../common/k8s-api/endpoints"; +} from "@k8slens/kube-object"; diff --git a/packages/core/src/features/cluster/kube-object-details/extension-api/disable-kube-object-detail-items-when-cluster-is-not-relevant.test.tsx b/packages/core/src/features/cluster/kube-object-details/extension-api/disable-kube-object-detail-items-when-cluster-is-not-relevant.test.tsx index bb928310c9..7b2d25d4c2 100644 --- a/packages/core/src/features/cluster/kube-object-details/extension-api/disable-kube-object-detail-items-when-cluster-is-not-relevant.test.tsx +++ b/packages/core/src/features/cluster/kube-object-details/extension-api/disable-kube-object-detail-items-when-cluster-is-not-relevant.test.tsx @@ -3,22 +3,15 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import type { RenderResult } from "@testing-library/react"; -import type { - ApplicationBuilder, -} from "../../../../renderer/components/test-utils/get-application-builder"; -import { - getApplicationBuilder, -} from "../../../../renderer/components/test-utils/get-application-builder"; +import type { ApplicationBuilder } from "../../../../renderer/components/test-utils/get-application-builder"; +import { getApplicationBuilder } from "../../../../renderer/components/test-utils/get-application-builder"; import React from "react"; -import { KubeObject } from "../../../../common/k8s-api/kube-object"; +import { KubeObject } from "@k8slens/kube-object"; import apiManagerInjectable from "../../../../common/k8s-api/api-manager/manager.injectable"; import type { KubeObjectStore } from "../../../../common/k8s-api/kube-object.store"; import type { KubeApi } from "../../../../common/k8s-api/kube-api"; -import showDetailsInjectable - from "../../../../renderer/components/kube-detail-params/show-details.injectable"; -import type { - FakeExtensionOptions, -} from "../../../../renderer/components/test-utils/get-extension-fake"; +import showDetailsInjectable from "../../../../renderer/components/kube-detail-params/show-details.injectable"; +import type { FakeExtensionOptions } from "../../../../renderer/components/test-utils/get-extension-fake"; import { observable } from "mobx"; describe("disable kube object detail items when cluster is not relevant", () => { @@ -37,7 +30,7 @@ describe("disable kube object detail items when cluster is not relevant", () => } as Partial> as KubeApi; const store = { api, - loadFromPath: async () => getKubeObjectStub("some-kind", "some-api-version"), + loadFromPath: async () => Promise.resolve(getKubeObjectStub("some-kind", "some-api-version")), getByPath() { }, } as Partial> as KubeObjectStore; diff --git a/packages/core/src/features/cluster/kube-object-details/extension-api/reactively-hide-kube-object-detail-item.test.tsx b/packages/core/src/features/cluster/kube-object-details/extension-api/reactively-hide-kube-object-detail-item.test.tsx index f589916b37..c901100bc8 100644 --- a/packages/core/src/features/cluster/kube-object-details/extension-api/reactively-hide-kube-object-detail-item.test.tsx +++ b/packages/core/src/features/cluster/kube-object-details/extension-api/reactively-hide-kube-object-detail-item.test.tsx @@ -8,7 +8,7 @@ import { getApplicationBuilder } from "../../../../renderer/components/test-util import type { IObservableValue } from "mobx"; import { runInAction, computed, observable } from "mobx"; import React from "react"; -import { KubeObject } from "../../../../common/k8s-api/kube-object"; +import { KubeObject } from "@k8slens/kube-object"; import apiManagerInjectable from "../../../../common/k8s-api/api-manager/manager.injectable"; import type { KubeObjectStore } from "../../../../common/k8s-api/kube-object.store"; import type { KubeApi } from "../../../../common/k8s-api/kube-api"; @@ -33,7 +33,7 @@ describe("reactively hide kube object detail item", () => { } as Partial> as KubeApi; const store = { api, - loadFromPath: async () => getKubeObjectStub("some-kind", "some-api-version"), + loadFromPath: async () => Promise.resolve(getKubeObjectStub("some-kind", "some-api-version")), getByPath() { }, } as Partial> as KubeObjectStore; diff --git a/packages/core/src/features/cluster/kube-object-menu/extension-api/disable-kube-object-menu-items-when-cluster-is-not-relevant.test.tsx b/packages/core/src/features/cluster/kube-object-menu/extension-api/disable-kube-object-menu-items-when-cluster-is-not-relevant.test.tsx index e0284e7534..6763d589ca 100644 --- a/packages/core/src/features/cluster/kube-object-menu/extension-api/disable-kube-object-menu-items-when-cluster-is-not-relevant.test.tsx +++ b/packages/core/src/features/cluster/kube-object-menu/extension-api/disable-kube-object-menu-items-when-cluster-is-not-relevant.test.tsx @@ -14,7 +14,7 @@ import { computed, runInAction } from "mobx"; import React from "react"; import { navigateToRouteInjectionToken } from "../../../../common/front-end-routing/navigate-to-route-injection-token"; import { routeSpecificComponentInjectionToken } from "../../../../renderer/routes/route-specific-component-injection-token"; -import { KubeObject } from "../../../../common/k8s-api/kube-object"; +import { KubeObject } from "@k8slens/kube-object"; import { KubeObjectMenu } from "../../../../renderer/components/kube-object-menu"; describe("disable kube object menu items when cluster is not relevant", () => { diff --git a/packages/core/src/features/cluster/kube-object-menu/extension-api/reactively-hide-kube-object-menu-item.test.tsx b/packages/core/src/features/cluster/kube-object-menu/extension-api/reactively-hide-kube-object-menu-item.test.tsx index a50bd15c53..68d2a27e87 100644 --- a/packages/core/src/features/cluster/kube-object-menu/extension-api/reactively-hide-kube-object-menu-item.test.tsx +++ b/packages/core/src/features/cluster/kube-object-menu/extension-api/reactively-hide-kube-object-menu-item.test.tsx @@ -12,7 +12,7 @@ import { observable, runInAction, computed } from "mobx"; import React from "react"; import { navigateToRouteInjectionToken } from "../../../../common/front-end-routing/navigate-to-route-injection-token"; import { routeSpecificComponentInjectionToken } from "../../../../renderer/routes/route-specific-component-injection-token"; -import { KubeObject } from "../../../../common/k8s-api/kube-object"; +import { KubeObject } from "@k8slens/kube-object"; import { KubeObjectMenu } from "../../../../renderer/components/kube-object-menu"; describe("reactively hide kube object menu item", () => { diff --git a/packages/core/src/features/cluster/kube-object-status-icon/extension-api/disable-kube-object-statuses-when-cluster-is-not-relevant.test.tsx b/packages/core/src/features/cluster/kube-object-status-icon/extension-api/disable-kube-object-statuses-when-cluster-is-not-relevant.test.tsx index 9afbdd55fb..d109ffef93 100644 --- a/packages/core/src/features/cluster/kube-object-status-icon/extension-api/disable-kube-object-statuses-when-cluster-is-not-relevant.test.tsx +++ b/packages/core/src/features/cluster/kube-object-status-icon/extension-api/disable-kube-object-statuses-when-cluster-is-not-relevant.test.tsx @@ -15,7 +15,7 @@ import React from "react"; import { navigateToRouteInjectionToken } from "../../../../common/front-end-routing/navigate-to-route-injection-token"; import { routeSpecificComponentInjectionToken } from "../../../../renderer/routes/route-specific-component-injection-token"; import { KubeObjectStatusIcon } from "../../../../renderer/components/kube-object-status-icon/kube-object-status-icon"; -import { KubeObject } from "../../../../common/k8s-api/kube-object"; +import { KubeObject } from "@k8slens/kube-object"; import { KubeObjectStatusLevel } from "../../../../common/k8s-api/kube-object-status"; describe("disable kube object statuses when cluster is not relevant", () => { diff --git a/packages/core/src/features/cluster/kube-object-status-icon/extension-api/reactively-hide-kube-object-status.test.tsx b/packages/core/src/features/cluster/kube-object-status-icon/extension-api/reactively-hide-kube-object-status.test.tsx index 09158cc318..eca417fca5 100644 --- a/packages/core/src/features/cluster/kube-object-status-icon/extension-api/reactively-hide-kube-object-status.test.tsx +++ b/packages/core/src/features/cluster/kube-object-status-icon/extension-api/reactively-hide-kube-object-status.test.tsx @@ -12,7 +12,7 @@ import { observable, runInAction, computed } from "mobx"; import React from "react"; import { navigateToRouteInjectionToken } from "../../../../common/front-end-routing/navigate-to-route-injection-token"; import { routeSpecificComponentInjectionToken } from "../../../../renderer/routes/route-specific-component-injection-token"; -import { KubeObject } from "../../../../common/k8s-api/kube-object"; +import { KubeObject } from "@k8slens/kube-object"; import { KubeObjectStatusLevel } from "../../../../common/k8s-api/kube-object-status"; import { KubeObjectStatusIcon } from "../../../../renderer/components/kube-object-status-icon"; diff --git a/packages/core/src/features/cluster/kube-object-status-icon/show-status-for-a-kube-object.test.tsx b/packages/core/src/features/cluster/kube-object-status-icon/show-status-for-a-kube-object.test.tsx index 8a8ee4ee7e..8a730933e4 100644 --- a/packages/core/src/features/cluster/kube-object-status-icon/show-status-for-a-kube-object.test.tsx +++ b/packages/core/src/features/cluster/kube-object-status-icon/show-status-for-a-kube-object.test.tsx @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { KubeObjectStatusLevel } from "../../../common/k8s-api/kube-object-status"; -import { KubeObject } from "../../../common/k8s-api/kube-object"; +import { KubeObject } from "@k8slens/kube-object"; import React from "react"; import type { DiContainer } from "@ogre-tools/injectable"; import { getInjectable } from "@ogre-tools/injectable"; diff --git a/packages/core/src/features/cluster/namespaces/edit-namespace-from-new-tab.test.tsx b/packages/core/src/features/cluster/namespaces/edit-namespace-from-new-tab.test.tsx index 3d0651b848..5fde13873c 100644 --- a/packages/core/src/features/cluster/namespaces/edit-namespace-from-new-tab.test.tsx +++ b/packages/core/src/features/cluster/namespaces/edit-namespace-from-new-tab.test.tsx @@ -13,7 +13,7 @@ import getRandomIdForEditResourceTabInjectable from "../../../renderer/component import type { AsyncFnMock } from "@async-fn/jest"; import asyncFn from "@async-fn/jest"; import dockStoreInjectable from "../../../renderer/components/dock/dock/store.injectable"; -import { Namespace } from "../../../common/k8s-api/endpoints"; +import { Namespace } from "@k8slens/kube-object"; import showSuccessNotificationInjectable from "../../../renderer/components/notifications/show-success-notification.injectable"; import showErrorNotificationInjectable from "../../../renderer/components/notifications/show-error-notification.injectable"; import readJsonFileInjectable from "../../../common/fs/read-json-file.injectable"; @@ -23,8 +23,7 @@ import type { ApiKubePatch } from "../../../renderer/k8s/api-kube-patch.injectab import type { ApiKubeGet } from "../../../renderer/k8s/api-kube-get.injectable"; import apiKubePatchInjectable from "../../../renderer/k8s/api-kube-patch.injectable"; import apiKubeGetInjectable from "../../../renderer/k8s/api-kube-get.injectable"; -import type { KubeJsonApiData } from "../../../common/k8s-api/kube-json-api"; -import type { BaseKubeJsonApiObjectMetadata, KubeObjectScope } from "../../../common/k8s-api/kube-object"; +import type { BaseKubeJsonApiObjectMetadata, KubeObjectScope, KubeJsonApiData } from "@k8slens/kube-object"; import { JsonApiErrorParsed } from "../../../common/k8s-api/json-api"; import type { ShowNotification } from "../../../renderer/components/notifications"; import React from "react"; @@ -542,9 +541,9 @@ metadata: readJsonFileInjectable, ); - const actual = (await readJsonFile( + const actual = await readJsonFile( "/some-directory-for-lens-local-storage/some-cluster-id.json", - )) as any; + ) as Record>; expect( actual.edit_resource_store["some-first-tab-id"], diff --git a/packages/core/src/features/cluster/namespaces/edit-namespace-from-previously-opened-tab.test.tsx b/packages/core/src/features/cluster/namespaces/edit-namespace-from-previously-opened-tab.test.tsx index 07a907ac82..99e80c22a1 100644 --- a/packages/core/src/features/cluster/namespaces/edit-namespace-from-previously-opened-tab.test.tsx +++ b/packages/core/src/features/cluster/namespaces/edit-namespace-from-previously-opened-tab.test.tsx @@ -13,7 +13,7 @@ import requestKubeResourceInjectable from "../../../renderer/components/dock/edi import directoryForLensLocalStorageInjectable from "../../../common/directory-for-lens-local-storage/directory-for-lens-local-storage.injectable"; import writeJsonFileInjectable from "../../../common/fs/write-json-file.injectable"; import { TabKind } from "../../../renderer/components/dock/dock/store"; -import { Namespace } from "../../../common/k8s-api/endpoints"; +import { Namespace } from "@k8slens/kube-object"; describe("cluster/namespaces - edit namespaces from previously opened tab", () => { let builder: ApplicationBuilder; diff --git a/packages/core/src/features/cluster/workloads/pods.test.tsx b/packages/core/src/features/cluster/workloads/pods.test.tsx index 15f62c9b2b..e5aa1190f1 100644 --- a/packages/core/src/features/cluster/workloads/pods.test.tsx +++ b/packages/core/src/features/cluster/workloads/pods.test.tsx @@ -7,9 +7,11 @@ import type { RenderResult } from "@testing-library/react"; import navigateToPodsInjectable from "../../../common/front-end-routing/routes/cluster/workloads/pods/navigate-to-pods.injectable"; import { type ApplicationBuilder, getApplicationBuilder } from "../../../renderer/components/test-utils/get-application-builder"; import podStoreInjectable from "../../../renderer/components/workloads-pods/store.injectable"; -import type { PodMetrics } from "../../../common/k8s-api/endpoints"; -import { Pod } from "../../../common/k8s-api/endpoints"; +import type { PodMetrics, PodStatus } from "@k8slens/kube-object"; +import { Pod } from "@k8slens/kube-object"; +import type { PodMetricsApi } from "../../../common/k8s-api/endpoints/pod-metrics.api"; import podMetricsApiInjectable from "../../../common/k8s-api/endpoints/pod-metrics.api.injectable"; +import type { RequestMetrics } from "../../../common/k8s-api/endpoints/metrics.api/request-metrics.injectable"; import requestMetricsInjectable from "../../../common/k8s-api/endpoints/metrics.api/request-metrics.injectable"; import apiManagerInjectable from "../../../common/k8s-api/api-manager/manager.injectable"; @@ -18,7 +20,7 @@ describe("workloads / pods", () => { let applicationBuilder: ApplicationBuilder; const podMetrics: PodMetrics[] = []; - beforeEach(async () => { + beforeEach(() => { applicationBuilder = getApplicationBuilder().setEnvironmentToClusterFrame(); applicationBuilder.namespaces.add("default"); applicationBuilder.beforeWindowStart(({ windowDi }) => { @@ -28,8 +30,8 @@ describe("workloads / pods", () => { }); windowDi.override(podMetricsApiInjectable, () => ({ - list: async () => podMetrics, - } as any)); + list: async () => Promise.resolve(podMetrics), + } as PodMetricsApi)); const apiManager = windowDi.inject(apiManagerInjectable); const podStore = windowDi.inject(podStoreInjectable); @@ -79,7 +81,7 @@ describe("workloads / pods", () => { expect(rendered.baseElement).toMatchSnapshot(); }); - it("shows item list is empty", async () => { + it("shows item list is empty", () => { expect(rendered.getByText("Item list is empty")).toBeInTheDocument(); }); }); @@ -87,7 +89,7 @@ describe("workloads / pods", () => { describe("given a namespace has pods", () => { beforeEach(async () => { applicationBuilder.afterWindowStart(({ windowDi }) => { - windowDi.override(requestMetricsInjectable, () => () => ({} as any)); + windowDi.override(requestMetricsInjectable, () => (() => Promise.resolve({})) as unknown as RequestMetrics); const podStore = windowDi.inject(podStoreInjectable); @@ -111,7 +113,7 @@ describe("workloads / pods", () => { }, ], }, - status: {} as any, + status: {} as PodStatus, })); podStore.isLoaded = true; }); diff --git a/packages/core/src/features/pod-logs/download-logs.test.tsx b/packages/core/src/features/pod-logs/download-logs.test.tsx index 6cd46f0d3d..8bc95f78f5 100644 --- a/packages/core/src/features/pod-logs/download-logs.test.tsx +++ b/packages/core/src/features/pod-logs/download-logs.test.tsx @@ -27,7 +27,7 @@ import stopLoadingLogsInjectable from "../../renderer/components/dock/logs/stop- import { dockerPod } from "../../renderer/components/dock/logs/__test__/pod.mock"; import showErrorNotificationInjectable from "../../renderer/components/notifications/show-error-notification.injectable"; import type { DiContainer } from "@ogre-tools/injectable"; -import type { Container } from "../../common/k8s-api/endpoints"; +import type { Container } from "@k8slens/kube-object"; describe("download logs options in logs dock tab", () => { let windowDi: DiContainer; @@ -36,7 +36,7 @@ describe("download logs options in logs dock tab", () => { let openSaveFileDialogMock: jest.MockedFunction<() => void>; let callForLogsMock: jest.MockedFunction; let getLogsMock: jest.Mock; - let getSplittedLogsMock: jest.Mock; + let getSplitLogsMock: jest.Mock; let showErrorNotificationMock: jest.Mock; const logs = new Map([["timestamp", "some-logs"]]); const pod = dockerPod; @@ -55,7 +55,7 @@ describe("download logs options in logs dock tab", () => { callForLogsMock = jest.fn(); getLogsMock = jest.fn(); - getSplittedLogsMock = jest.fn(); + getSplitLogsMock = jest.fn(); builder.beforeWindowStart(({ windowDi }) => { windowDi.override(callForLogsInjectable, () => callForLogsMock); @@ -63,7 +63,7 @@ describe("download logs options in logs dock tab", () => { // Overriding internals of logsViewModelInjectable windowDi.override(getLogsInjectable, () => getLogsMock); windowDi.override(getLogsWithoutTimestampsInjectable, () => getLogsMock); - windowDi.override(getTimestampSplitLogsInjectable, () => getSplittedLogsMock); + windowDi.override(getTimestampSplitLogsInjectable, () => getSplitLogsMock); windowDi.override(reloadLogsInjectable, () => jest.fn()); windowDi.override(getLogTabDataInjectable, () => () => ({ selectedPodId: selectedPod.getId(), @@ -106,11 +106,11 @@ describe("download logs options in logs dock tab", () => { }); describe("when logs not available", () => { - beforeEach(async () => { + beforeEach(() => { const createLogsTab = windowDi.inject(createPodLogsTabInjectable); getLogsMock.mockReturnValue([]); - getSplittedLogsMock.mockReturnValue([]); + getSplitLogsMock.mockReturnValue([]); createLogsTab({ selectedPod: pod, @@ -130,11 +130,11 @@ describe("download logs options in logs dock tab", () => { }); describe("when logs available", () => { - beforeEach(async () => { + beforeEach(() => { const createLogsTab = windowDi.inject(createPodLogsTabInjectable); getLogsMock.mockReturnValue(["some-logs"]); - getSplittedLogsMock.mockReturnValue([...logs]); + getSplitLogsMock.mockReturnValue([...logs]); createLogsTab({ selectedPod: pod, @@ -174,13 +174,13 @@ describe("download logs options in logs dock tab", () => { }); describe("when call for all logs resolves with logs", () => { - beforeEach(async () => { + beforeEach(() => { callForLogsMock.mockResolvedValue("all-logs"); }); describe("when selected 'download all logs'", () => { - beforeEach(async () => { - await act(async () => { + beforeEach(() => { + act(() => { const button = rendered.getByTestId("download-all-logs"); button.click(); @@ -194,11 +194,11 @@ describe("download logs options in logs dock tab", () => { ); }); - it("shows save dialog with proper attributes", async () => { + it("shows save dialog with proper attributes", () => { expect(openSaveFileDialogMock).toHaveBeenCalledWith("docker-exporter.log", "all-logs", "text/plain"); }); - it("doesn't block download dropdown for interaction after click", async () => { + it("doesn't block download dropdown for interaction after click", () => { expect(rendered.getByTestId("download-logs-dropdown")).not.toHaveAttribute("disabled"); }); }); @@ -225,13 +225,13 @@ describe("download logs options in logs dock tab", () => { }); describe("when call for logs resolves with no logs", () => { - beforeEach(async () => { + beforeEach(() => { callForLogsMock.mockResolvedValue(""); }); describe("when selected 'download all logs'", () => { - beforeEach(async () => { - await act(async () => { + beforeEach(() => { + act(() => { const button = rendered.getByTestId("download-all-logs"); button.click(); @@ -254,8 +254,8 @@ describe("download logs options in logs dock tab", () => { }); describe("when selected 'download all logs'", () => { - beforeEach(async () => { - await act(async () => { + beforeEach(() => { + act(() => { const button = rendered.getByTestId("download-all-logs"); button.click(); @@ -269,7 +269,7 @@ describe("download logs options in logs dock tab", () => { ); }); - it("doesn't show save dialog", async () => { + it("doesn't show save dialog", () => { expect(openSaveFileDialogMock).not.toHaveBeenCalled(); }); }); diff --git a/packages/core/src/main/helm/helm-service/get-helm-release-resources/call-for-helm-manifest/call-for-helm-manifest.injectable.ts b/packages/core/src/main/helm/helm-service/get-helm-release-resources/call-for-helm-manifest/call-for-helm-manifest.injectable.ts index 75115b4c27..b5ad835c25 100644 --- a/packages/core/src/main/helm/helm-service/get-helm-release-resources/call-for-helm-manifest/call-for-helm-manifest.injectable.ts +++ b/packages/core/src/main/helm/helm-service/get-helm-release-resources/call-for-helm-manifest/call-for-helm-manifest.injectable.ts @@ -6,7 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable"; import type { AsyncResult } from "@k8slens/utilities"; import execHelmInjectable from "../../../exec-helm/exec-helm.injectable"; import yaml from "js-yaml"; -import type { KubeJsonApiData, KubeJsonApiDataList } from "../../../../../common/k8s-api/kube-json-api"; +import type { KubeJsonApiData, KubeJsonApiDataList } from "@k8slens/kube-object"; const callForHelmManifestInjectable = getInjectable({ id: "call-for-helm-manifest", diff --git a/packages/core/src/main/helm/helm-service/get-helm-release-resources/call-for-kube-resources-by-manifest/call-for-kube-resources-by-manifest.injectable.ts b/packages/core/src/main/helm/helm-service/get-helm-release-resources/call-for-kube-resources-by-manifest/call-for-kube-resources-by-manifest.injectable.ts index 637a4788a8..5e0a074e03 100644 --- a/packages/core/src/main/helm/helm-service/get-helm-release-resources/call-for-kube-resources-by-manifest/call-for-kube-resources-by-manifest.injectable.ts +++ b/packages/core/src/main/helm/helm-service/get-helm-release-resources/call-for-kube-resources-by-manifest/call-for-kube-resources-by-manifest.injectable.ts @@ -9,7 +9,7 @@ import execFileWithInputInjectable from "./exec-file-with-input/exec-file-with-i import { getErrorMessage } from "../../../../../common/utils/get-error-message"; import { map } from "lodash/fp"; import { pipeline } from "@ogre-tools/fp"; -import type { KubeJsonApiData } from "../../../../../common/k8s-api/kube-json-api"; +import type { KubeJsonApiData } from "@k8slens/kube-object"; export type CallForKubeResourcesByManifest = ( namespace: string, diff --git a/packages/core/src/main/helm/helm-service/get-helm-release-resources/get-helm-release-resources.injectable.ts b/packages/core/src/main/helm/helm-service/get-helm-release-resources/get-helm-release-resources.injectable.ts index cef66508be..02e6f0f08c 100644 --- a/packages/core/src/main/helm/helm-service/get-helm-release-resources/get-helm-release-resources.injectable.ts +++ b/packages/core/src/main/helm/helm-service/get-helm-release-resources/get-helm-release-resources.injectable.ts @@ -4,7 +4,7 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import callForHelmManifestInjectable from "./call-for-helm-manifest/call-for-helm-manifest.injectable"; -import type { KubeJsonApiData, KubeJsonApiDataList } from "../../../../common/k8s-api/kube-json-api"; +import type { KubeJsonApiData, KubeJsonApiDataList } from "@k8slens/kube-object"; import type { AsyncResult } from "@k8slens/utilities"; export type GetHelmReleaseResources = ( diff --git a/packages/core/src/main/helm/helm-service/get-helm-release-resources/get-helm-release-resources.test.ts b/packages/core/src/main/helm/helm-service/get-helm-release-resources/get-helm-release-resources.test.ts index 7f2f194a75..b8cb38d578 100644 --- a/packages/core/src/main/helm/helm-service/get-helm-release-resources/get-helm-release-resources.test.ts +++ b/packages/core/src/main/helm/helm-service/get-helm-release-resources/get-helm-release-resources.test.ts @@ -13,7 +13,7 @@ import asyncFn from "@async-fn/jest"; import type { ExecFileWithInput } from "./call-for-kube-resources-by-manifest/exec-file-with-input/exec-file-with-input.injectable"; import execFileWithInputInjectable from "./call-for-kube-resources-by-manifest/exec-file-with-input/exec-file-with-input.injectable"; import type { AsyncResult } from "@k8slens/utilities"; -import type { KubeJsonApiData } from "../../../../common/k8s-api/kube-json-api"; +import type { KubeJsonApiData } from "@k8slens/kube-object"; describe("get helm release resources", () => { let getHelmReleaseResources: GetHelmReleaseResources; diff --git a/packages/core/src/main/shell-session/node-shell-session/node-shell-session.ts b/packages/core/src/main/shell-session/node-shell-session/node-shell-session.ts index a9a928adb6..bb3653e786 100644 --- a/packages/core/src/main/shell-session/node-shell-session/node-shell-session.ts +++ b/packages/core/src/main/shell-session/node-shell-session/node-shell-session.ts @@ -15,6 +15,7 @@ import type { CreateKubeJsonApiForCluster } from "../../../common/k8s-api/create import type { CreateKubeApi } from "../../../common/k8s-api/create-kube-api.injectable"; import { initialNodeShellImage } from "../../../common/cluster-types"; import type { LoadProxyKubeconfig } from "../../cluster/load-proxy-kubeconfig.injectable"; +import type { Pod } from "@k8slens/kube-object"; export interface NodeShellSessionArgs extends ShellSessionArgs { nodeName: string; @@ -148,13 +149,13 @@ export class NodeShellSession extends ShellSession { .watch(`/api/v1/namespaces/kube-system/pods`, {}, // callback is called for each received object. - (type, { metadata: { name }, status }) => { + (type, { metadata: { name }, status }: Pod) => { if (name === this.podName) { - switch (status.phase) { + switch (status?.phase) { case "Running": return resolve(); case "Failed": - return reject(`Failed to be created: ${status.message || "unknown error"}`); + return reject(`Failed to be created: ${(status as unknown as Record).message || "unknown error"}`); } } }, @@ -167,12 +168,13 @@ export class NodeShellSession extends ShellSession { .then(req => { setTimeout(() => { this.dependencies.logger.error(`[NODE-SHELL]: aborting wait for ${this.podName}, timing out`); + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call req.abort(); reject("Pod creation timed out"); }, 2 * 60 * 1000); // 2 * 60 * 1000 }) .catch(error => { - this.dependencies.logger.error(`[NODE-SHELL]: waiting for ${this.podName} failed: ${error}`); + this.dependencies.logger.error(`[NODE-SHELL]: waiting for ${this.podName} failed: ${String(error)}`); reject(error); }); }); diff --git a/packages/core/src/renderer/before-frame-starts/runnables/setup-auto-crd-api-creations.injectable.ts b/packages/core/src/renderer/before-frame-starts/runnables/setup-auto-crd-api-creations.injectable.ts index 7bf41f200e..b414526a40 100644 --- a/packages/core/src/renderer/before-frame-starts/runnables/setup-auto-crd-api-creations.injectable.ts +++ b/packages/core/src/renderer/before-frame-starts/runnables/setup-auto-crd-api-creations.injectable.ts @@ -5,9 +5,9 @@ import { getInjectable } from "@ogre-tools/injectable"; import { reaction } from "mobx"; import { customResourceDefinitionApiInjectionToken } from "../../../common/k8s-api/api-manager/crd-api-token"; -import type { CustomResourceDefinition } from "../../../common/k8s-api/endpoints"; +import type { CustomResourceDefinition } from "@k8slens/kube-object"; import { KubeApi } from "../../../common/k8s-api/kube-api"; -import { KubeObject } from "../../../common/k8s-api/kube-object"; +import { KubeObject } from "@k8slens/kube-object"; import maybeKubeApiInjectable from "../../../common/k8s-api/maybe-kube-api.injectable"; import loggerInjectable from "../../../common/logger.injectable"; import { injectableDifferencingRegistratorWith } from "../../../common/utils/registrator-helper"; diff --git a/packages/core/src/renderer/components/__tests__/cronjob.store.test.ts b/packages/core/src/renderer/components/__tests__/cronjob.store.test.ts index cd99ebeabe..9c5c8cafa6 100644 --- a/packages/core/src/renderer/components/__tests__/cronjob.store.test.ts +++ b/packages/core/src/renderer/components/__tests__/cronjob.store.test.ts @@ -4,7 +4,7 @@ */ import type { CronJobStore } from "../workloads-cronjobs/store"; import cronJobStoreInjectable from "../workloads-cronjobs/store.injectable"; -import { CronJob } from "../../../common/k8s-api/endpoints"; +import { CronJob } from "@k8slens/kube-object"; import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable"; import { getDiForUnitTesting } from "../../getDiForUnitTesting"; import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable"; diff --git a/packages/core/src/renderer/components/__tests__/daemonset.store.test.ts b/packages/core/src/renderer/components/__tests__/daemonset.store.test.ts index 6b6a78223d..afed95c7a1 100644 --- a/packages/core/src/renderer/components/__tests__/daemonset.store.test.ts +++ b/packages/core/src/renderer/components/__tests__/daemonset.store.test.ts @@ -7,7 +7,7 @@ import { observable } from "mobx"; import type { DaemonSetStore } from "../workloads-daemonsets/store"; import daemonSetStoreInjectable from "../workloads-daemonsets/store.injectable"; import podStoreInjectable from "../workloads-pods/store.injectable"; -import { DaemonSet, Pod } from "../../../common/k8s-api/endpoints"; +import { DaemonSet, Pod } from "@k8slens/kube-object"; import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable"; import { getDiForUnitTesting } from "../../getDiForUnitTesting"; import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable"; diff --git a/packages/core/src/renderer/components/__tests__/deployments.store.test.ts b/packages/core/src/renderer/components/__tests__/deployments.store.test.ts index fe3de1c194..31a4460bd6 100644 --- a/packages/core/src/renderer/components/__tests__/deployments.store.test.ts +++ b/packages/core/src/renderer/components/__tests__/deployments.store.test.ts @@ -7,8 +7,8 @@ import { observable } from "mobx"; import type { DeploymentStore } from "../workloads-deployments/store"; import deploymentStoreInjectable from "../workloads-deployments/store.injectable"; import podStoreInjectable from "../workloads-pods/store.injectable"; -import type { PodSpec } from "../../../common/k8s-api/endpoints"; -import { Deployment, Pod } from "../../../common/k8s-api/endpoints"; +import type { PodSpec } from "@k8slens/kube-object"; +import { Deployment, Pod } from "@k8slens/kube-object"; import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable"; import { getDiForUnitTesting } from "../../getDiForUnitTesting"; import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable"; diff --git a/packages/core/src/renderer/components/__tests__/job.store.test.ts b/packages/core/src/renderer/components/__tests__/job.store.test.ts index f3d292efc7..1d43c32a5d 100644 --- a/packages/core/src/renderer/components/__tests__/job.store.test.ts +++ b/packages/core/src/renderer/components/__tests__/job.store.test.ts @@ -7,7 +7,7 @@ import { observable } from "mobx"; import type { JobStore } from "../workloads-jobs/store"; import jobStoreInjectable from "../workloads-jobs/store.injectable"; import podStoreInjectable from "../workloads-pods/store.injectable"; -import { Job, Pod } from "../../../common/k8s-api/endpoints"; +import { Job, Pod } from "@k8slens/kube-object"; import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable"; import { getDiForUnitTesting } from "../../getDiForUnitTesting"; import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable"; diff --git a/packages/core/src/renderer/components/__tests__/nodes.api.test.ts b/packages/core/src/renderer/components/__tests__/nodes.api.test.ts index c152dc7b18..8651f1fa63 100644 --- a/packages/core/src/renderer/components/__tests__/nodes.api.test.ts +++ b/packages/core/src/renderer/components/__tests__/nodes.api.test.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import { formatNodeTaint } from "../../../common/k8s-api/endpoints"; +import { formatNodeTaint } from "@k8slens/kube-object"; describe("formatNodeTaint tests", () => { it("should use value if defined", () => { diff --git a/packages/core/src/renderer/components/__tests__/pods.store.test.ts b/packages/core/src/renderer/components/__tests__/pods.store.test.ts index 9cd9478f6e..a8735d39c7 100644 --- a/packages/core/src/renderer/components/__tests__/pods.store.test.ts +++ b/packages/core/src/renderer/components/__tests__/pods.store.test.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import { Pod } from "../../../common/k8s-api/endpoints"; +import { Pod } from "@k8slens/kube-object"; import type { PodStore } from "../workloads-pods/store"; import podStoreInjectable from "../workloads-pods/store.injectable"; import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable"; diff --git a/packages/core/src/renderer/components/__tests__/replicaset.store.test.ts b/packages/core/src/renderer/components/__tests__/replicaset.store.test.ts index 89369a3968..296da6e6a2 100644 --- a/packages/core/src/renderer/components/__tests__/replicaset.store.test.ts +++ b/packages/core/src/renderer/components/__tests__/replicaset.store.test.ts @@ -7,7 +7,7 @@ import { observable } from "mobx"; import podStoreInjectable from "../workloads-pods/store.injectable"; import replicasetsStoreInjectable from "../workloads-replicasets/store.injectable"; import type { ReplicaSetStore } from "../workloads-replicasets/store"; -import { ReplicaSet, Pod } from "../../../common/k8s-api/endpoints"; +import { ReplicaSet, Pod } from "@k8slens/kube-object"; import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable"; import { getDiForUnitTesting } from "../../getDiForUnitTesting"; import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable"; diff --git a/packages/core/src/renderer/components/__tests__/statefulset.store.test.ts b/packages/core/src/renderer/components/__tests__/statefulset.store.test.ts index 81184bc74c..05c90c0beb 100644 --- a/packages/core/src/renderer/components/__tests__/statefulset.store.test.ts +++ b/packages/core/src/renderer/components/__tests__/statefulset.store.test.ts @@ -7,7 +7,7 @@ import { observable } from "mobx"; import podStoreInjectable from "../workloads-pods/store.injectable"; import type { StatefulSetStore } from "../workloads-statefulsets/store"; import statefulSetStoreInjectable from "../workloads-statefulsets/store.injectable"; -import { StatefulSet, Pod } from "../../../common/k8s-api/endpoints"; +import { StatefulSet, Pod } from "@k8slens/kube-object"; import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable"; import { getDiForUnitTesting } from "../../getDiForUnitTesting"; import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable"; diff --git a/packages/core/src/renderer/components/catalog/columns/default-category.injectable.tsx b/packages/core/src/renderer/components/catalog/columns/default-category.injectable.tsx index 9d2af2a5f3..e33ff1ce7f 100644 --- a/packages/core/src/renderer/components/catalog/columns/default-category.injectable.tsx +++ b/packages/core/src/renderer/components/catalog/columns/default-category.injectable.tsx @@ -6,7 +6,7 @@ import styles from "../catalog.module.scss"; import React from "react"; import type { RegisteredAdditionalCategoryColumn } from "../custom-category-columns"; -import { KubeObject } from "../../../../common/k8s-api/kube-object"; +import { KubeObject } from "@k8slens/kube-object"; import { getInjectable } from "@ogre-tools/injectable"; import getLabelBadgesInjectable from "../get-label-badges.injectable"; diff --git a/packages/core/src/renderer/components/catalog/get-label-badges.injectable.tsx b/packages/core/src/renderer/components/catalog/get-label-badges.injectable.tsx index b75e501f90..b7168734f1 100644 --- a/packages/core/src/renderer/components/catalog/get-label-badges.injectable.tsx +++ b/packages/core/src/renderer/components/catalog/get-label-badges.injectable.tsx @@ -7,7 +7,7 @@ import React from "react"; import { getInjectable } from "@ogre-tools/injectable"; import type { CatalogEntity } from "../../api/catalog-entity"; import searchUrlPageParamInjectable from "../input/search-url-page-param.injectable"; -import { KubeObject } from "../../../common/k8s-api/kube-object"; +import { KubeObject } from "@k8slens/kube-object"; import { Badge } from "../badge"; export type GetLabelBadges = (entity: CatalogEntity, onClick?: (evt: React.MouseEvent) => void) => JSX.Element[]; diff --git a/packages/core/src/renderer/components/cluster/cluster-issues.tsx b/packages/core/src/renderer/components/cluster/cluster-issues.tsx index d212463cfe..6b17ed7bc0 100644 --- a/packages/core/src/renderer/components/cluster/cluster-issues.tsx +++ b/packages/core/src/renderer/components/cluster/cluster-issues.tsx @@ -13,7 +13,6 @@ import { Icon } from "../icon"; import { SubHeader } from "../layout/sub-header"; import { Table, TableCell, TableHead, TableRow } from "../table"; import { cssNames, prevDefault } from "@k8slens/utilities"; -import type { ItemObject } from "@k8slens/list-layout"; import { Spinner } from "../spinner"; import type { ApiManager } from "../../../common/k8s-api/api-manager"; import { KubeObjectAge } from "../kube-object/age"; @@ -34,7 +33,9 @@ export interface ClusterIssuesProps { className?: string; } -interface Warning extends ItemObject { +interface Warning { + getId: () => string; + getName: () => string; kind: string; message: string | undefined; selfLink: string; @@ -70,8 +71,8 @@ class NonInjectedClusterIssues extends React.Component ({ selfLink: node.selfLink, - getId: node.getId, - getName: node.getName, + getId: () => node.getId(), + getName: () => node.getName(), kind: node.kind, message, renderAge: () => , diff --git a/packages/core/src/renderer/components/cluster/cluster-overview-store/cluster-overview-store.ts b/packages/core/src/renderer/components/cluster/cluster-overview-store/cluster-overview-store.ts index a15f47f0ef..0966e71b06 100644 --- a/packages/core/src/renderer/components/cluster/cluster-overview-store/cluster-overview-store.ts +++ b/packages/core/src/renderer/components/cluster/cluster-overview-store/cluster-overview-store.ts @@ -3,10 +3,11 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import { action, observable, reaction, when, makeObservable } from "mobx"; +import { action, observable, reaction, when, makeObservable, runInAction } from "mobx"; import type { KubeObjectStoreDependencies } from "../../../../common/k8s-api/kube-object.store"; import { KubeObjectStore } from "../../../../common/k8s-api/kube-object.store"; -import type { Cluster, ClusterApi } from "../../../../common/k8s-api/endpoints"; +import type { ClusterApi } from "../../../../common/k8s-api/endpoints"; +import type { Cluster } from "@k8slens/kube-object"; import type { StorageLayer } from "../../../utils/storage-helper"; import type { NodeStore } from "../../nodes/store"; import type { ClusterMetricData, RequestClusterMetricsByNodeNames } from "../../../../common/k8s-api/endpoints/metrics.api/request-cluster-metrics-by-node-names.injectable"; @@ -72,7 +73,7 @@ export class ClusterOverviewStore extends KubeObjectStore i reaction(() => this.metricNodeRole, () => { if (this.metrics) { this.resetMetrics(); - this.loadMetrics(); + void this.loadMetrics(); } }); @@ -85,13 +86,16 @@ export class ClusterOverviewStore extends KubeObjectStore i }); } - @action async loadMetrics(params?: RequestMetricsParams) { await when(() => this.dependencies.nodeStore.isLoaded); const { masterNodes, workerNodes } = this.dependencies.nodeStore; const nodes = this.metricNodeRole === MetricNodeRole.MASTER && masterNodes.length ? masterNodes : workerNodes; - this.metrics = await this.dependencies.requestClusterMetricsByNodeNames(nodes.map(node => node.getName()), params); + const metrics = await this.dependencies.requestClusterMetricsByNodeNames(nodes.map(node => node.getName()), params); + + runInAction(() => { + this.metrics = metrics; + }); } getMetricsValues(source: Partial): [number, string][] { diff --git a/packages/core/src/renderer/components/command-palette/registered-commands/registered-commands.injectable.ts b/packages/core/src/renderer/components/command-palette/registered-commands/registered-commands.injectable.ts index baddad1a50..65548823cb 100644 --- a/packages/core/src/renderer/components/command-palette/registered-commands/registered-commands.injectable.ts +++ b/packages/core/src/renderer/components/command-palette/registered-commands/registered-commands.injectable.ts @@ -6,7 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable"; import type { IComputedValue } from "mobx"; import { computed } from "mobx"; -import type { CustomResourceDefinition } from "../../../../common/k8s-api/endpoints"; +import type { CustomResourceDefinition } from "@k8slens/kube-object"; import customResourceDefinitionsInjectable from "../../custom-resources/custom-resources.injectable"; import type { LensRendererExtension } from "../../../../extensions/lens-renderer-extension"; import rendererExtensionsInjectable from "../../../../extensions/renderer-extensions.injectable"; @@ -33,6 +33,8 @@ const instantiateRegisteredCommands = ({ extensions, customResourceDefinitions, ]; for (const { scope, isActive = () => true, ...command } of commands) { + void scope; + if (!result.has(command.id)) { result.set(command.id, { ...command, isActive }); } diff --git a/packages/core/src/renderer/components/config-horizontal-pod-autoscalers/details.test.tsx b/packages/core/src/renderer/components/config-horizontal-pod-autoscalers/details.test.tsx index 79f96c28cb..be08c90352 100644 --- a/packages/core/src/renderer/components/config-horizontal-pod-autoscalers/details.test.tsx +++ b/packages/core/src/renderer/components/config-horizontal-pod-autoscalers/details.test.tsx @@ -7,7 +7,7 @@ import React from "react"; import directoryForKubeConfigsInjectable from "../../../common/app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable"; import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable"; import { Cluster } from "../../../common/cluster/cluster"; -import { HorizontalPodAutoscaler, HpaMetricType } from "../../../common/k8s-api/endpoints"; +import { HorizontalPodAutoscaler } from "@k8slens/kube-object"; import hostedClusterInjectable from "../../cluster-frame-context/hosted-cluster.injectable"; import { getDiForUnitTesting } from "../../getDiForUnitTesting"; import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable"; @@ -115,7 +115,7 @@ describe("", () => { ...hpaV2.spec, metrics: [ { - type: HpaMetricType.ContainerResource, + type: "ContainerResource", containerResource: { name: "cpu", container: "nginx", @@ -145,7 +145,7 @@ describe("", () => { ...hpaV2.spec, metrics: [ { - type: HpaMetricType.Resource, + type: "Resource", resource: { name: "cpu", target: { @@ -174,7 +174,7 @@ describe("", () => { ...hpaV2.spec, metrics: [ { - type: HpaMetricType.Pods, + type: "Pods", pods: { metric: { name: "packets-per-second", @@ -205,7 +205,7 @@ describe("", () => { ...hpaV2.spec, metrics: [ { - type: HpaMetricType.Pods, + type: "Pods", pods: { metricName: "packets-per-second", }, @@ -230,7 +230,7 @@ describe("", () => { ...hpaV2.spec, metrics: [ { - type: HpaMetricType.Object, + type: "Object", object: { metric: { name: "requests-per-second", @@ -266,7 +266,7 @@ describe("", () => { ...hpaV2.spec, metrics: [ { - type: HpaMetricType.Object, + type: "Object", object: { metricName: "requests-per-second", }, @@ -291,7 +291,7 @@ describe("", () => { ...hpaV2.spec, metrics: [ { - type: HpaMetricType.External, + type: "External", external: { metric: { name: "queue_messages_ready", @@ -325,7 +325,7 @@ describe("", () => { ...hpaV2.spec, metrics: [ { - type: HpaMetricType.External, + type: "External", external: { metricName: "queue_messages_ready", metricSelector: { diff --git a/packages/core/src/renderer/components/config-horizontal-pod-autoscalers/details.tsx b/packages/core/src/renderer/components/config-horizontal-pod-autoscalers/details.tsx index f4d675fa3d..251aa31e7c 100644 --- a/packages/core/src/renderer/components/config-horizontal-pod-autoscalers/details.tsx +++ b/packages/core/src/renderer/components/config-horizontal-pod-autoscalers/details.tsx @@ -12,8 +12,8 @@ import { DrawerItem, DrawerTitle } from "../drawer"; import { Badge } from "../badge"; import type { KubeObjectDetailsProps } from "../kube-object-details"; import { cssNames } from "@k8slens/utilities"; -import type { HorizontalPodAutoscalerMetricSpec, HorizontalPodAutoscalerMetricTarget } from "../../../common/k8s-api/endpoints/horizontal-pod-autoscaler.api"; -import { HorizontalPodAutoscaler, HpaMetricType } from "../../../common/k8s-api/endpoints/horizontal-pod-autoscaler.api"; +import type { HorizontalPodAutoscalerMetricSpec, HorizontalPodAutoscalerMetricTarget } from "@k8slens/kube-object"; +import { HorizontalPodAutoscaler } from "@k8slens/kube-object"; import { Table, TableCell, TableHead, TableRow } from "../table"; import type { ApiManager } from "../../../common/k8s-api/api-manager"; import type { Logger } from "../../../common/logger"; @@ -63,18 +63,18 @@ class NonInjectedHorizontalPodAutoscalerDetails extends React.Component {metricName} @@ -83,8 +83,8 @@ class NonInjectedHorizontalPodAutoscalerDetails extends React.Component ); } - case HpaMetricType.External: - return `${metricName} on ${JSON.stringify(metric.external.metricSelector ?? metric.external.metric?.selector)}`; + case "External": + return `${metricName ?? ""} on ${JSON.stringify(metric.external.metricSelector ?? metric.external.metric?.selector)}`; default: return hpa.spec?.targetCPUUtilizationPercentage ? "CPU Utilization percentage" : "unknown"; } diff --git a/packages/core/src/renderer/components/config-horizontal-pod-autoscalers/get-metric-name.ts b/packages/core/src/renderer/components/config-horizontal-pod-autoscalers/get-metric-name.ts index d45db05ec0..b352d35891 100644 --- a/packages/core/src/renderer/components/config-horizontal-pod-autoscalers/get-metric-name.ts +++ b/packages/core/src/renderer/components/config-horizontal-pod-autoscalers/get-metric-name.ts @@ -3,8 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import { HpaMetricType } from "../../../common/k8s-api/endpoints"; -import type { LabelSelector } from "../../../common/k8s-api/kube-object"; +import type { HpaMetricType, LabelSelector } from "@k8slens/kube-object"; type MetricNames = Partial(parser: Type, current: HorizontalPodAutoscalerMetricStatus | undefined, target: HorizontalPodAutoscalerMetricSpec) { switch (target.type) { - case HpaMetricType.Resource: + case "Resource": return parser.getResource({ current: current?.resource, target: target.resource }); - case HpaMetricType.Pods: + case "Pods": return parser.getPods({ current: current?.pods, target: target.pods }); - case HpaMetricType.Object: + case "Object": return parser.getObject({ current: current?.object, target: target.object }); - case HpaMetricType.External: + case "External": return parser.getExternal({ current: current?.external, target: target.external }); - case HpaMetricType.ContainerResource: + case "ContainerResource": return parser.getContainerResource({ current: current?.containerResource, target: target.containerResource }); default: return {}; diff --git a/packages/core/src/renderer/components/config-horizontal-pod-autoscalers/list-view.tsx b/packages/core/src/renderer/components/config-horizontal-pod-autoscalers/list-view.tsx index b70723580a..4fd0a1c668 100644 --- a/packages/core/src/renderer/components/config-horizontal-pod-autoscalers/list-view.tsx +++ b/packages/core/src/renderer/components/config-horizontal-pod-autoscalers/list-view.tsx @@ -8,7 +8,7 @@ import "./list-view.scss"; import React from "react"; import { observer } from "mobx-react"; import { KubeObjectListLayout } from "../kube-object-list-layout"; -import type { HorizontalPodAutoscaler } from "../../../common/k8s-api/endpoints/horizontal-pod-autoscaler.api"; +import type { HorizontalPodAutoscaler } from "@k8slens/kube-object"; import { Badge } from "../badge"; import { cssNames } from "@k8slens/utilities"; import { KubeObjectStatusIcon } from "../kube-object-status-icon"; diff --git a/packages/core/src/renderer/components/config-horizontal-pod-autoscalers/metric-parser-v1.ts b/packages/core/src/renderer/components/config-horizontal-pod-autoscalers/metric-parser-v1.ts index d5eee6b261..9f212bdf45 100644 --- a/packages/core/src/renderer/components/config-horizontal-pod-autoscalers/metric-parser-v1.ts +++ b/packages/core/src/renderer/components/config-horizontal-pod-autoscalers/metric-parser-v1.ts @@ -2,7 +2,7 @@ * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ -import type { MetricCurrentTarget, V2Beta1ContainerResourceMetricSource, V2Beta1ContainerResourceMetricStatus, V2Beta1ExternalMetricSource, V2Beta1ExternalMetricStatus, V2Beta1ObjectMetricSource, V2Beta1ObjectMetricStatus, V2Beta1PodsMetricSource, V2Beta1PodsMetricStatus, V2Beta1ResourceMetricSource, V2Beta1ResourceMetricStatus } from "../../../common/k8s-api/endpoints"; +import type { MetricCurrentTarget, V2Beta1ContainerResourceMetricSource, V2Beta1ContainerResourceMetricStatus, V2Beta1ExternalMetricSource, V2Beta1ExternalMetricStatus, V2Beta1ObjectMetricSource, V2Beta1ObjectMetricStatus, V2Beta1PodsMetricSource, V2Beta1PodsMetricStatus, V2Beta1ResourceMetricSource, V2Beta1ResourceMetricStatus } from "@k8slens/kube-object"; export class HorizontalPodAutoscalerV1MetricParser { public getResource({ current, target }: { current: V2Beta1ResourceMetricStatus | undefined; target: V2Beta1ResourceMetricSource }): MetricCurrentTarget { @@ -19,14 +19,14 @@ export class HorizontalPodAutoscalerV1MetricParser { ), }; } - + public getPods({ current, target }: { current: V2Beta1PodsMetricStatus | undefined; target: V2Beta1PodsMetricSource }): MetricCurrentTarget { return { current: current?.currentAverageValue, target: target?.targetAverageValue, }; } - + public getObject({ current, target }: { current: V2Beta1ObjectMetricStatus | undefined; target: V2Beta1ObjectMetricSource }): MetricCurrentTarget { return { current: ( @@ -39,7 +39,7 @@ export class HorizontalPodAutoscalerV1MetricParser { ), }; } - + public getExternal({ current, target }: { current: V2Beta1ExternalMetricStatus | undefined; target: V2Beta1ExternalMetricSource }): MetricCurrentTarget { return { current: ( @@ -52,7 +52,7 @@ export class HorizontalPodAutoscalerV1MetricParser { ), }; } - + public getContainerResource({ current, target }: { current: V2Beta1ContainerResourceMetricStatus | undefined; target: V2Beta1ContainerResourceMetricSource }): MetricCurrentTarget { return { current: ( diff --git a/packages/core/src/renderer/components/config-horizontal-pod-autoscalers/metric-parser-v2.ts b/packages/core/src/renderer/components/config-horizontal-pod-autoscalers/metric-parser-v2.ts index 9b47b13780..615ada23a0 100644 --- a/packages/core/src/renderer/components/config-horizontal-pod-autoscalers/metric-parser-v2.ts +++ b/packages/core/src/renderer/components/config-horizontal-pod-autoscalers/metric-parser-v2.ts @@ -2,7 +2,7 @@ * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ -import type { MetricCurrentTarget, V2ContainerResourceMetricSource, V2ContainerResourceMetricStatus, V2ExternalMetricSource, V2ExternalMetricStatus, V2ObjectMetricSource, V2ObjectMetricStatus, V2PodsMetricSource, V2PodsMetricStatus, V2ResourceMetricSource, V2ResourceMetricStatus } from "../../../common/k8s-api/endpoints"; +import type { MetricCurrentTarget, V2ContainerResourceMetricSource, V2ContainerResourceMetricStatus, V2ExternalMetricSource, V2ExternalMetricStatus, V2ObjectMetricSource, V2ObjectMetricStatus, V2PodsMetricSource, V2PodsMetricStatus, V2ResourceMetricSource, V2ResourceMetricStatus } from "@k8slens/kube-object"; export class HorizontalPodAutoscalerV2MetricParser { public getResource({ current, target }: { current: V2ResourceMetricStatus | undefined; target: V2ResourceMetricSource }): MetricCurrentTarget { @@ -17,14 +17,14 @@ export class HorizontalPodAutoscalerV2MetricParser { : target?.target?.averageValue, }; } - + public getPods({ current, target }: { current: V2PodsMetricStatus | undefined; target: V2PodsMetricSource }): MetricCurrentTarget { return { current: current?.current?.averageValue, target: target?.target?.averageValue, }; } - + public getObject({ current, target }: { current: V2ObjectMetricStatus | undefined; target: V2ObjectMetricSource }): MetricCurrentTarget { return { current: ( @@ -37,7 +37,7 @@ export class HorizontalPodAutoscalerV2MetricParser { ), }; } - + public getExternal({ current, target }: { current: V2ExternalMetricStatus | undefined; target: V2ExternalMetricSource }): MetricCurrentTarget { const currentAverage = current?.current?.averageValue ? `${current?.current?.averageValue} (avg)` : undefined; const targetAverage = target?.target?.averageValue ? `${target?.target?.averageValue} (avg)` : undefined; @@ -53,16 +53,16 @@ export class HorizontalPodAutoscalerV2MetricParser { ), }; } - + public getContainerResource({ current, target }: { current: V2ContainerResourceMetricStatus | undefined; target: V2ContainerResourceMetricSource }): MetricCurrentTarget { return { current: ( current?.current?.averageValue - ?? current?.current?.averageUtilization ? `${current?.current?.averageUtilization}%` : undefined + ?? (current?.current?.averageUtilization ? `${current.current.averageUtilization}%` : undefined) ), target: ( target?.target?.averageValue - ?? target?.target?.averageUtilization ? `${target?.target?.averageUtilization}%` : undefined + ?? (target?.target?.averageUtilization ? `${target.target.averageUtilization}%` : undefined) ), }; } diff --git a/packages/core/src/renderer/components/config-horizontal-pod-autoscalers/metric-parser.test.ts b/packages/core/src/renderer/components/config-horizontal-pod-autoscalers/metric-parser.test.ts index 2168da52ad..c96ea48c29 100644 --- a/packages/core/src/renderer/components/config-horizontal-pod-autoscalers/metric-parser.test.ts +++ b/packages/core/src/renderer/components/config-horizontal-pod-autoscalers/metric-parser.test.ts @@ -5,7 +5,7 @@ import type { DiContainer } from "@ogre-tools/injectable"; import getHorizontalPodAutoscalerMetrics from "./get-metrics.injectable"; import { getDiForUnitTesting } from "../../getDiForUnitTesting"; -import { HorizontalPodAutoscaler, HpaMetricType } from "../../../common/k8s-api/endpoints"; +import { HorizontalPodAutoscaler } from "@k8slens/kube-object"; const hpaV2 = { apiVersion: "autoscaling/v2", @@ -71,7 +71,7 @@ describe("getHorizontalPodAutoscalerMetrics", () => { ...hpaV2.spec, metrics: [ { - type: HpaMetricType.Resource, + type: "Resource", resource: { name: "cpu", target: { @@ -94,7 +94,7 @@ describe("getHorizontalPodAutoscalerMetrics", () => { ...hpaV2.spec, metrics: [ { - type: HpaMetricType.Resource, + type: "Resource", resource: { name: "cpu", target: { @@ -110,7 +110,7 @@ describe("getHorizontalPodAutoscalerMetrics", () => { desiredReplicas: 10, currentMetrics: [ { - type: HpaMetricType.Resource, + type: "Resource", resource: { name: "cpu", current: { @@ -133,7 +133,7 @@ describe("getHorizontalPodAutoscalerMetrics", () => { ...hpaV2.spec, metrics: [ { - type: HpaMetricType.Resource, + type: "Resource", resource: { name: "cpu", target: { @@ -149,7 +149,7 @@ describe("getHorizontalPodAutoscalerMetrics", () => { desiredReplicas: 10, currentMetrics: [ { - type: HpaMetricType.Resource, + type: "Resource", resource: { name: "cpu", current: { @@ -172,7 +172,7 @@ describe("getHorizontalPodAutoscalerMetrics", () => { ...hpaV2.spec, metrics: [ { - type: HpaMetricType.ContainerResource, + type: "ContainerResource", containerResource: { name: "cpu", container: "nginx", @@ -198,7 +198,7 @@ describe("getHorizontalPodAutoscalerMetrics", () => { ...hpaV2.spec, metrics: [ { - type: HpaMetricType.ContainerResource, + type: "ContainerResource", containerResource: { name: "cpu", container: "nginx", @@ -215,7 +215,7 @@ describe("getHorizontalPodAutoscalerMetrics", () => { desiredReplicas: 10, currentMetrics: [ { - type: HpaMetricType.ContainerResource, + type: "ContainerResource", containerResource: { name: "cpu", current: { @@ -239,7 +239,7 @@ describe("getHorizontalPodAutoscalerMetrics", () => { ...hpaV2.spec, metrics: [ { - type: HpaMetricType.Pods, + type: "Pods", pods: { metric: { name: "packets-per-second", @@ -266,7 +266,7 @@ describe("getHorizontalPodAutoscalerMetrics", () => { ...hpaV2.spec, metrics: [ { - type: HpaMetricType.Pods, + type: "Pods", pods: { metric: { name: "packets-per-second", @@ -284,7 +284,7 @@ describe("getHorizontalPodAutoscalerMetrics", () => { desiredReplicas: 10, currentMetrics: [ { - type: HpaMetricType.Pods, + type: "Pods", pods: { metric: { name: "packets-per-second", @@ -310,7 +310,7 @@ describe("getHorizontalPodAutoscalerMetrics", () => { ...hpaV2.spec, metrics: [ { - type: HpaMetricType.Object, + type: "Object", object: { metric: { name: "requests-per-second", @@ -337,7 +337,7 @@ describe("getHorizontalPodAutoscalerMetrics", () => { ...hpaV2.spec, metrics: [ { - type: HpaMetricType.Object, + type: "Object", object: { metric: { name: "requests-per-second", @@ -364,7 +364,7 @@ describe("getHorizontalPodAutoscalerMetrics", () => { ...hpaV2.spec, metrics: [ { - type: HpaMetricType.Object, + type: "Object", object: { metric: { name: "requests-per-second", @@ -382,7 +382,7 @@ describe("getHorizontalPodAutoscalerMetrics", () => { desiredReplicas: 10, currentMetrics: [ { - type: HpaMetricType.Object, + type: "Object", object: { metric: { name: "requests-per-second", @@ -408,7 +408,7 @@ describe("getHorizontalPodAutoscalerMetrics", () => { ...hpaV2.spec, metrics: [ { - type: HpaMetricType.External, + type: "External", external: { metric: { name: "queue_messages_ready", @@ -438,7 +438,7 @@ describe("getHorizontalPodAutoscalerMetrics", () => { ...hpaV2.spec, metrics: [ { - type: HpaMetricType.External, + type: "External", external: { metric: { name: "queue_messages_ready", @@ -468,7 +468,7 @@ describe("getHorizontalPodAutoscalerMetrics", () => { ...hpaV2.spec, metrics: [ { - type: HpaMetricType.External, + type: "External", external: { metric: { name: "queue_messages_ready", @@ -489,7 +489,7 @@ describe("getHorizontalPodAutoscalerMetrics", () => { desiredReplicas: 10, currentMetrics: [ { - type: HpaMetricType.External, + type: "External", external: { metric: { name: "queue_messages_ready", @@ -515,7 +515,7 @@ describe("getHorizontalPodAutoscalerMetrics", () => { ...hpaV2.spec, metrics: [ { - type: HpaMetricType.External, + type: "External", external: { metric: { name: "queue_messages_ready", @@ -536,7 +536,7 @@ describe("getHorizontalPodAutoscalerMetrics", () => { desiredReplicas: 10, currentMetrics: [ { - type: HpaMetricType.External, + type: "External", external: { metric: { name: "queue_messages_ready", @@ -562,7 +562,7 @@ describe("getHorizontalPodAutoscalerMetrics", () => { ...hpaV2.spec, metrics: [ { - type: HpaMetricType.External, + type: "External", external: { metric: { name: "queue_messages_ready", @@ -583,7 +583,7 @@ describe("getHorizontalPodAutoscalerMetrics", () => { desiredReplicas: 10, currentMetrics: [ { - type: HpaMetricType.External, + type: "External", external: { metric: { name: "queue_messages_NOT_ready", @@ -616,7 +616,7 @@ describe("getHorizontalPodAutoscalerMetrics", () => { ...hpaV2Beta1.spec, metrics: [ { - type: HpaMetricType.Resource, + type: "Resource", resource: { name: "cpu", targetAverageUtilization: 50, @@ -636,7 +636,7 @@ describe("getHorizontalPodAutoscalerMetrics", () => { ...hpaV2Beta1.spec, metrics: [ { - type: HpaMetricType.Resource, + type: "Resource", resource: { name: "cpu", targetAverageUtilization: 50, @@ -649,7 +649,7 @@ describe("getHorizontalPodAutoscalerMetrics", () => { desiredReplicas: 10, currentMetrics: [ { - type: HpaMetricType.Resource, + type: "Resource", resource: { name: "cpu", currentAverageUtilization: 10, @@ -669,7 +669,7 @@ describe("getHorizontalPodAutoscalerMetrics", () => { ...hpaV2Beta1.spec, metrics: [ { - type: HpaMetricType.Resource, + type: "Resource", resource: { name: "cpu", targetAverageValue: "100m", @@ -682,7 +682,7 @@ describe("getHorizontalPodAutoscalerMetrics", () => { desiredReplicas: 10, currentMetrics: [ { - type: HpaMetricType.Resource, + type: "Resource", resource: { name: "cpu", currentAverageValue: "500m", @@ -703,7 +703,7 @@ describe("getHorizontalPodAutoscalerMetrics", () => { ...hpaV2Beta1.spec, metrics: [ { - type: HpaMetricType.ContainerResource, + type: "ContainerResource", containerResource: { name: "cpu", container: "nginx", @@ -726,7 +726,7 @@ describe("getHorizontalPodAutoscalerMetrics", () => { ...hpaV2Beta1.spec, metrics: [ { - type: HpaMetricType.ContainerResource, + type: "ContainerResource", containerResource: { name: "cpu", container: "nginx", @@ -740,7 +740,7 @@ describe("getHorizontalPodAutoscalerMetrics", () => { desiredReplicas: 10, currentMetrics: [ { - type: HpaMetricType.ContainerResource, + type: "ContainerResource", containerResource: { name: "cpu", currentAverageUtilization: 10, @@ -762,7 +762,7 @@ describe("getHorizontalPodAutoscalerMetrics", () => { ...hpaV2Beta1.spec, metrics: [ { - type: HpaMetricType.Pods, + type: "Pods", pods: { metricName: "packets-per-second", targetAverageValue: "1k", @@ -784,7 +784,7 @@ describe("getHorizontalPodAutoscalerMetrics", () => { ...hpaV2Beta1.spec, metrics: [ { - type: HpaMetricType.Pods, + type: "Pods", pods: { metricName: "packets-per-second", @@ -798,7 +798,7 @@ describe("getHorizontalPodAutoscalerMetrics", () => { desiredReplicas: 10, currentMetrics: [ { - type: HpaMetricType.Pods, + type: "Pods", pods: { metricName: "packets-per-second", currentAverageValue: "10", @@ -820,7 +820,7 @@ describe("getHorizontalPodAutoscalerMetrics", () => { ...hpaV2Beta1.spec, metrics: [ { - type: HpaMetricType.Object, + type: "Object", object: { metricName: "packets-per-second", targetValue: "10k", @@ -842,7 +842,7 @@ describe("getHorizontalPodAutoscalerMetrics", () => { ...hpaV2Beta1.spec, metrics: [ { - type: HpaMetricType.Object, + type: "Object", object: { metricName: "packets-per-second", averageValue: "5k", @@ -864,7 +864,7 @@ describe("getHorizontalPodAutoscalerMetrics", () => { ...hpaV2Beta1.spec, metrics: [ { - type: HpaMetricType.Object, + type: "Object", object: { metricName: "packets-per-second", targetValue: "5k", @@ -877,7 +877,7 @@ describe("getHorizontalPodAutoscalerMetrics", () => { desiredReplicas: 10, currentMetrics: [ { - type: HpaMetricType.Object, + type: "Object", object: { metricName: "packets-per-second", currentValue: "10k", @@ -899,7 +899,7 @@ describe("getHorizontalPodAutoscalerMetrics", () => { ...hpaV2Beta1.spec, metrics: [ { - type: HpaMetricType.External, + type: "External", external: { metricName: "queue_messages_ready", metricSelector: { matchLabels: { queue: "worker_tasks" }}, @@ -922,7 +922,7 @@ describe("getHorizontalPodAutoscalerMetrics", () => { ...hpaV2Beta1.spec, metrics: [ { - type: HpaMetricType.External, + type: "External", external: { metricName: "queue_messages_ready", metricSelector: { matchLabels: { queue: "worker_tasks" }}, @@ -945,7 +945,7 @@ describe("getHorizontalPodAutoscalerMetrics", () => { ...hpaV2Beta1.spec, metrics: [ { - type: HpaMetricType.External, + type: "External", external: { metricName: "queue_messages_ready", metricSelector: { matchLabels: { queue: "worker_tasks" }}, @@ -959,7 +959,7 @@ describe("getHorizontalPodAutoscalerMetrics", () => { desiredReplicas: 10, currentMetrics: [ { - type: HpaMetricType.External, + type: "External", external: { metricName: "queue_messages_ready", currentValue: "10", @@ -981,7 +981,7 @@ describe("getHorizontalPodAutoscalerMetrics", () => { ...hpaV2Beta1.spec, metrics: [ { - type: HpaMetricType.External, + type: "External", external: { metricName: "queue_messages_ready", metricSelector: { matchLabels: { queue: "worker_tasks" }}, @@ -995,7 +995,7 @@ describe("getHorizontalPodAutoscalerMetrics", () => { desiredReplicas: 10, currentMetrics: [ { - type: HpaMetricType.External, + type: "External", external: { metricName: "queue_messages_ready", currentAverageValue: "10", @@ -1016,7 +1016,7 @@ describe("getHorizontalPodAutoscalerMetrics", () => { ...hpaV2Beta1.spec, metrics: [ { - type: HpaMetricType.Resource, + type: "Resource", resource: { name: "cpu", targetAverageUtilization: 50, @@ -1029,7 +1029,7 @@ describe("getHorizontalPodAutoscalerMetrics", () => { desiredReplicas: 10, currentMetrics: [ { - type: HpaMetricType.Resource, + type: "Resource", resource: { name: "memory", currentAverageUtilization: 10, diff --git a/packages/core/src/renderer/components/config-horizontal-pod-autoscalers/store.ts b/packages/core/src/renderer/components/config-horizontal-pod-autoscalers/store.ts index ccee4ab943..dca39d9384 100644 --- a/packages/core/src/renderer/components/config-horizontal-pod-autoscalers/store.ts +++ b/packages/core/src/renderer/components/config-horizontal-pod-autoscalers/store.ts @@ -4,7 +4,8 @@ */ import { KubeObjectStore } from "../../../common/k8s-api/kube-object.store"; -import type { HorizontalPodAutoscaler, HorizontalPodAutoscalerApi } from "../../../common/k8s-api/endpoints/horizontal-pod-autoscaler.api"; +import type { HorizontalPodAutoscalerApi } from "../../../common/k8s-api/endpoints/horizontal-pod-autoscaler.api"; +import type { HorizontalPodAutoscaler } from "@k8slens/kube-object"; export class HorizontalPodAutoscalerStore extends KubeObjectStore { } diff --git a/packages/core/src/renderer/components/config-leases/lease-details.tsx b/packages/core/src/renderer/components/config-leases/lease-details.tsx index d86ecabaf8..1a943df617 100644 --- a/packages/core/src/renderer/components/config-leases/lease-details.tsx +++ b/packages/core/src/renderer/components/config-leases/lease-details.tsx @@ -9,7 +9,7 @@ import React from "react"; import { observer } from "mobx-react"; import { DrawerItem } from "../drawer"; import type { KubeObjectDetailsProps } from "../kube-object-details"; -import type { Lease } from "../../../common/k8s-api/endpoints"; +import type { Lease } from "@k8slens/kube-object"; export interface LeaseDetailsProps extends KubeObjectDetailsProps { } diff --git a/packages/core/src/renderer/components/config-leases/leases.tsx b/packages/core/src/renderer/components/config-leases/leases.tsx index 5f877ed54f..f9ac5259bb 100644 --- a/packages/core/src/renderer/components/config-leases/leases.tsx +++ b/packages/core/src/renderer/components/config-leases/leases.tsx @@ -7,7 +7,7 @@ import "./leases.scss"; import * as React from "react"; import { observer } from "mobx-react"; -import type { Lease } from "../../../common/k8s-api/endpoints/lease.api"; +import type { Lease } from "@k8slens/kube-object"; import { KubeObjectStatusIcon } from "../kube-object-status-icon"; import type { KubeObjectDetailsProps } from "../kube-object-details"; import { KubeObjectListLayout } from "../kube-object-list-layout"; diff --git a/packages/core/src/renderer/components/config-leases/store.ts b/packages/core/src/renderer/components/config-leases/store.ts index db19aed8dd..86b956190f 100644 --- a/packages/core/src/renderer/components/config-leases/store.ts +++ b/packages/core/src/renderer/components/config-leases/store.ts @@ -4,7 +4,8 @@ */ import { KubeObjectStore } from "../../../common/k8s-api/kube-object.store"; -import type { Lease, LeaseApi } from "../../../common/k8s-api/endpoints/lease.api"; +import type { LeaseApi } from "../../../common/k8s-api/endpoints/lease.api"; +import type { Lease } from "@k8slens/kube-object"; export class LeaseStore extends KubeObjectStore { } diff --git a/packages/core/src/renderer/components/config-limit-ranges/limit-range-details.tsx b/packages/core/src/renderer/components/config-limit-ranges/limit-range-details.tsx index 79c3038a5d..eb404b9745 100644 --- a/packages/core/src/renderer/components/config-limit-ranges/limit-range-details.tsx +++ b/packages/core/src/renderer/components/config-limit-ranges/limit-range-details.tsx @@ -8,8 +8,8 @@ import "./limit-range-details.scss"; import React from "react"; import { observer } from "mobx-react"; import type { KubeObjectDetailsProps } from "../kube-object-details"; -import type { LimitRangeItem } from "../../../common/k8s-api/endpoints/limit-range.api"; -import { LimitPart, LimitRange, Resource } from "../../../common/k8s-api/endpoints/limit-range.api"; +import type { LimitRangeItem } from "@k8slens/kube-object"; +import { LimitPart, LimitRange, Resource } from "@k8slens/kube-object"; import { DrawerItem } from "../drawer/drawer-item"; import { Badge } from "../badge"; import type { Logger } from "../../../common/logger"; diff --git a/packages/core/src/renderer/components/config-limit-ranges/store.ts b/packages/core/src/renderer/components/config-limit-ranges/store.ts index b38b0b30b5..80d96555f1 100644 --- a/packages/core/src/renderer/components/config-limit-ranges/store.ts +++ b/packages/core/src/renderer/components/config-limit-ranges/store.ts @@ -4,7 +4,8 @@ */ import { KubeObjectStore } from "../../../common/k8s-api/kube-object.store"; -import type { LimitRange, LimitRangeApi } from "../../../common/k8s-api/endpoints/limit-range.api"; +import type { LimitRangeApi } from "../../../common/k8s-api/endpoints/limit-range.api"; +import type { LimitRange } from "@k8slens/kube-object"; export class LimitRangeStore extends KubeObjectStore { } diff --git a/packages/core/src/renderer/components/config-maps/config-map-details.tsx b/packages/core/src/renderer/components/config-maps/config-map-details.tsx index a770f6d324..d4d65b179f 100644 --- a/packages/core/src/renderer/components/config-maps/config-map-details.tsx +++ b/packages/core/src/renderer/components/config-maps/config-map-details.tsx @@ -12,7 +12,7 @@ import { DrawerTitle } from "../drawer"; import type { ShowNotification } from "../notifications"; import { Button } from "@k8slens/button"; import type { KubeObjectDetailsProps } from "../kube-object-details"; -import { ConfigMap } from "../../../common/k8s-api/endpoints"; +import { ConfigMap } from "@k8slens/kube-object"; import type { Logger } from "../../../common/logger"; import type { ConfigMapStore } from "./store"; import { withInjectables } from "@ogre-tools/injectable-react"; @@ -42,7 +42,7 @@ class NonInjectedConfigMapDetails extends React.Component { const { object: configMap } = this.props; @@ -54,27 +54,29 @@ class NonInjectedConfigMapDetails extends React.Component { + save = () => { const { object: configMap, configMapStore } = this.props; - try { - this.isSaving = true; - await configMapStore.update(configMap, { - ...configMap, - data: Object.fromEntries(this.data), - }); - this.props.showSuccessNotification(( -

- {"ConfigMap "} - {configMap.getName()} - {" successfully updated."} -

- )); - } catch (error) { - this.props.showErrorNotification(`Failed to save config map: ${error}`); - } finally { - this.isSaving = false; - } + void (async () => { + try { + this.isSaving = true; + await configMapStore.update(configMap, { + ...configMap, + data: Object.fromEntries(this.data), + }); + this.props.showSuccessNotification(( +

+ {"ConfigMap "} + {configMap.getName()} + {" successfully updated."} +

+ )); + } catch (error) { + this.props.showErrorNotification(`Failed to save config map: ${String(error)}`); + } finally { + this.isSaving = false; + } + })(); }; render() { diff --git a/packages/core/src/renderer/components/config-maps/store.ts b/packages/core/src/renderer/components/config-maps/store.ts index 3688f9fda7..2774f3be4b 100644 --- a/packages/core/src/renderer/components/config-maps/store.ts +++ b/packages/core/src/renderer/components/config-maps/store.ts @@ -4,7 +4,8 @@ */ import { KubeObjectStore } from "../../../common/k8s-api/kube-object.store"; -import type { ConfigMap, ConfigMapApi, ConfigMapData } from "../../../common/k8s-api/endpoints/config-map.api"; +import type { ConfigMap, ConfigMapData } from "@k8slens/kube-object"; +import type { ConfigMapApi } from "../../../common/k8s-api/endpoints"; export class ConfigMapStore extends KubeObjectStore { } diff --git a/packages/core/src/renderer/components/config-mutating-webhook-configurations/details.test.tsx b/packages/core/src/renderer/components/config-mutating-webhook-configurations/details.test.tsx index c02b3047a7..510205c1ad 100644 --- a/packages/core/src/renderer/components/config-mutating-webhook-configurations/details.test.tsx +++ b/packages/core/src/renderer/components/config-mutating-webhook-configurations/details.test.tsx @@ -4,15 +4,14 @@ */ import type { RenderResult } from "@testing-library/react"; import React from "react"; -import { - MutatingWebhookConfiguration, -} from "../../../common/k8s-api/endpoints"; +import type { MutatingWebhookConfigurationData } from "@k8slens/kube-object"; +import { MutatingWebhookConfiguration } from "@k8slens/kube-object"; import { getDiForUnitTesting } from "../../getDiForUnitTesting"; import type { DiRender } from "../test-utils/renderFor"; import { renderFor } from "../test-utils/renderFor"; import { MutatingWebhookDetails } from "./mutating-webhook-configurations-details"; -const mutatingWebhookConfig = { +const mutatingWebhookConfig: MutatingWebhookConfigurationData = { apiVersion: "admissionregistration.k8s.io/v1", kind: "MutatingWebhookConfiguration", metadata: { @@ -51,7 +50,7 @@ describe("MutatingWebhookConfigsDetails", () => { let result: RenderResult; let render: DiRender; - beforeEach(async () => { + beforeEach(() => { const di = getDiForUnitTesting(); render = renderFor(di); diff --git a/packages/core/src/renderer/components/config-mutating-webhook-configurations/mutating-webhook-configuration-store.ts b/packages/core/src/renderer/components/config-mutating-webhook-configurations/mutating-webhook-configuration-store.ts index fbd30b10e3..dbbdbccd55 100644 --- a/packages/core/src/renderer/components/config-mutating-webhook-configurations/mutating-webhook-configuration-store.ts +++ b/packages/core/src/renderer/components/config-mutating-webhook-configurations/mutating-webhook-configuration-store.ts @@ -2,21 +2,9 @@ * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ -import type { - MutatingWebhookConfiguration, - MutatingWebhookConfigurationApi, -} from "../../../common/k8s-api/endpoints"; -import type { - KubeObjectStoreDependencies, - KubeObjectStoreOptions, -} from "../../../common/k8s-api/kube-object.store"; +import type { MutatingWebhookConfiguration } from "@k8slens/kube-object"; +import type { MutatingWebhookConfigurationApi } from "../../../common/k8s-api/endpoints"; import { KubeObjectStore } from "../../../common/k8s-api/kube-object.store"; -export interface MutatingWebhookConfigurationStoreDependencies extends KubeObjectStoreDependencies { -} - export class MutatingWebhookConfigurationStore extends KubeObjectStore { - constructor(protected readonly dependencies: MutatingWebhookConfigurationStoreDependencies, api: MutatingWebhookConfigurationApi, opts?: KubeObjectStoreOptions) { - super(dependencies, api, opts); - } } diff --git a/packages/core/src/renderer/components/config-mutating-webhook-configurations/mutating-webhook-configurations-details.tsx b/packages/core/src/renderer/components/config-mutating-webhook-configurations/mutating-webhook-configurations-details.tsx index 594ee3c43e..5d871ba147 100644 --- a/packages/core/src/renderer/components/config-mutating-webhook-configurations/mutating-webhook-configurations-details.tsx +++ b/packages/core/src/renderer/components/config-mutating-webhook-configurations/mutating-webhook-configurations-details.tsx @@ -6,7 +6,7 @@ import React from "react"; import { observer } from "mobx-react"; import { DrawerItem, DrawerTitle } from "../drawer"; import type { KubeObjectDetailsProps } from "../kube-object-details"; -import type { MutatingWebhookConfiguration } from "../../../common/k8s-api/endpoints"; +import type { MutatingWebhookConfiguration } from "@k8slens/kube-object"; import { WebhookConfig } from "./webhook-config"; export interface MutatingWebhookDetailsProps extends KubeObjectDetailsProps { diff --git a/packages/core/src/renderer/components/config-mutating-webhook-configurations/webhook-config.tsx b/packages/core/src/renderer/components/config-mutating-webhook-configurations/webhook-config.tsx index f33d50c6ec..9f46170308 100644 --- a/packages/core/src/renderer/components/config-mutating-webhook-configurations/webhook-config.tsx +++ b/packages/core/src/renderer/components/config-mutating-webhook-configurations/webhook-config.tsx @@ -4,12 +4,12 @@ */ import React from "react"; import styles from "./webhook-config.module.css"; -import type { MutatingWebhook, ValidatingWebhook } from "../../../common/k8s-api/endpoints"; +import type { Webhook } from "@k8slens/kube-object"; import { DrawerItem } from "../drawer"; import { Badge } from "../badge"; interface WebhookProps { - webhook: ValidatingWebhook | MutatingWebhook; + webhook: Webhook; } export const WebhookConfig: React.FC = ({ webhook }) => { @@ -82,7 +82,7 @@ export const WebhookConfig: React.FC = ({ webhook }) => {
Match Labels:
{Object.entries(webhook.namespaceSelector.matchLabels).map(([key, value], index) => ( - + ))}
@@ -118,7 +118,7 @@ export const WebhookConfig: React.FC = ({ webhook }) => {
Match Labels:
{Object.entries(webhook.objectSelector.matchLabels).map(([key, value], index) => ( - + ))}
diff --git a/packages/core/src/renderer/components/config-pod-disruption-budgets/__tests__/pod-distruption-budgets.test.tsx b/packages/core/src/renderer/components/config-pod-disruption-budgets/__tests__/pod-distruption-budgets.test.tsx index 804e199b6a..ef0887141f 100644 --- a/packages/core/src/renderer/components/config-pod-disruption-budgets/__tests__/pod-distruption-budgets.test.tsx +++ b/packages/core/src/renderer/components/config-pod-disruption-budgets/__tests__/pod-distruption-budgets.test.tsx @@ -4,7 +4,7 @@ */ import React from "react"; -import { PodDisruptionBudget } from "../../../../common/k8s-api/endpoints"; +import { PodDisruptionBudget } from "@k8slens/kube-object"; import { getDiForUnitTesting } from "../../../getDiForUnitTesting"; import { renderFor } from "../../test-utils/renderFor"; import { PodDisruptionBudgets } from "../pod-disruption-budgets"; @@ -106,7 +106,7 @@ describe("", () => { expect(result.container.querySelector(".TableRow .min-available")?.textContent).toEqual("N/A"); }); - + it("should display maxUnavailable as 0", () => { di.override(podDisruptionBudgetStoreInjectable, () => getPodDisruptionBudgetStoreInjectableMock(pdb)); const result = renderFor(di)(); diff --git a/packages/core/src/renderer/components/config-pod-disruption-budgets/pod-disruption-budgets-details.tsx b/packages/core/src/renderer/components/config-pod-disruption-budgets/pod-disruption-budgets-details.tsx index 1ce45e42d0..5fb5b20085 100644 --- a/packages/core/src/renderer/components/config-pod-disruption-budgets/pod-disruption-budgets-details.tsx +++ b/packages/core/src/renderer/components/config-pod-disruption-budgets/pod-disruption-budgets-details.tsx @@ -10,7 +10,7 @@ import { observer } from "mobx-react"; import { DrawerItem } from "../drawer"; import { Badge } from "../badge"; import type { KubeObjectDetailsProps } from "../kube-object-details"; -import type { PodDisruptionBudget } from "../../../common/k8s-api/endpoints"; +import type { PodDisruptionBudget } from "@k8slens/kube-object"; export interface PodDisruptionBudgetDetailsProps extends KubeObjectDetailsProps { } diff --git a/packages/core/src/renderer/components/config-pod-disruption-budgets/pod-disruption-budgets.tsx b/packages/core/src/renderer/components/config-pod-disruption-budgets/pod-disruption-budgets.tsx index b925d945a5..e48f757ede 100644 --- a/packages/core/src/renderer/components/config-pod-disruption-budgets/pod-disruption-budgets.tsx +++ b/packages/core/src/renderer/components/config-pod-disruption-budgets/pod-disruption-budgets.tsx @@ -7,7 +7,7 @@ import "./pod-disruption-budgets.scss"; import * as React from "react"; import { observer } from "mobx-react"; -import type { PodDisruptionBudget } from "../../../common/k8s-api/endpoints/pod-disruption-budget.api"; +import type { PodDisruptionBudget } from "@k8slens/kube-object"; import { KubeObjectStatusIcon } from "../kube-object-status-icon"; import type { KubeObjectDetailsProps } from "../kube-object-details"; import { KubeObjectListLayout } from "../kube-object-list-layout"; diff --git a/packages/core/src/renderer/components/config-pod-disruption-budgets/store.ts b/packages/core/src/renderer/components/config-pod-disruption-budgets/store.ts index c2907da95c..cdb97187ac 100644 --- a/packages/core/src/renderer/components/config-pod-disruption-budgets/store.ts +++ b/packages/core/src/renderer/components/config-pod-disruption-budgets/store.ts @@ -4,7 +4,8 @@ */ import { KubeObjectStore } from "../../../common/k8s-api/kube-object.store"; -import type { PodDisruptionBudget, PodDisruptionBudgetApi } from "../../../common/k8s-api/endpoints/pod-disruption-budget.api"; +import type { PodDisruptionBudgetApi } from "../../../common/k8s-api/endpoints/pod-disruption-budget.api"; +import type { PodDisruptionBudget } from "@k8slens/kube-object"; export class PodDisruptionBudgetStore extends KubeObjectStore { } diff --git a/packages/core/src/renderer/components/config-priority-classes/priority-classes-details.tsx b/packages/core/src/renderer/components/config-priority-classes/priority-classes-details.tsx index 7caf5190a1..4836365987 100644 --- a/packages/core/src/renderer/components/config-priority-classes/priority-classes-details.tsx +++ b/packages/core/src/renderer/components/config-priority-classes/priority-classes-details.tsx @@ -9,7 +9,7 @@ import React from "react"; import { observer } from "mobx-react"; import { DrawerItem } from "../drawer"; import type { KubeObjectDetailsProps } from "../kube-object-details"; -import type { PriorityClass } from "../../../common/k8s-api/endpoints"; +import type { PriorityClass } from "@k8slens/kube-object"; export interface PriorityClassesDetailsProps extends KubeObjectDetailsProps { } diff --git a/packages/core/src/renderer/components/config-priority-classes/priority-classes.tsx b/packages/core/src/renderer/components/config-priority-classes/priority-classes.tsx index 23a1dc6267..f167d6b06e 100644 --- a/packages/core/src/renderer/components/config-priority-classes/priority-classes.tsx +++ b/packages/core/src/renderer/components/config-priority-classes/priority-classes.tsx @@ -7,7 +7,7 @@ import "./priority-classes.scss"; import * as React from "react"; import { observer } from "mobx-react"; -import type { PriorityClass } from "../../../common/k8s-api/endpoints/priority-class.api"; +import type { PriorityClass } from "@k8slens/kube-object"; import { KubeObjectStatusIcon } from "../kube-object-status-icon"; import type { KubeObjectDetailsProps } from "../kube-object-details"; import { KubeObjectListLayout } from "../kube-object-list-layout"; diff --git a/packages/core/src/renderer/components/config-priority-classes/store.ts b/packages/core/src/renderer/components/config-priority-classes/store.ts index f11fdce838..40d0c796d9 100644 --- a/packages/core/src/renderer/components/config-priority-classes/store.ts +++ b/packages/core/src/renderer/components/config-priority-classes/store.ts @@ -4,7 +4,8 @@ */ import { KubeObjectStore } from "../../../common/k8s-api/kube-object.store"; -import type { PriorityClass, PriorityClassApi } from "../../../common/k8s-api/endpoints/priority-class.api"; +import type { PriorityClassApi } from "../../../common/k8s-api/endpoints/priority-class.api"; +import type { PriorityClass } from "@k8slens/kube-object"; export class PriorityClassStore extends KubeObjectStore { } diff --git a/packages/core/src/renderer/components/config-resource-quotas/add-dialog/view.tsx b/packages/core/src/renderer/components/config-resource-quotas/add-dialog/view.tsx index 26b4c9f846..067c6f08ff 100644 --- a/packages/core/src/renderer/components/config-resource-quotas/add-dialog/view.tsx +++ b/packages/core/src/renderer/components/config-resource-quotas/add-dialog/view.tsx @@ -14,7 +14,8 @@ import { Dialog } from "../../dialog"; import { Wizard, WizardStep } from "../../wizard"; import { Input } from "../../input"; import { systemName } from "../../input/input_validators"; -import type { IResourceQuotaValues, ResourceQuotaApi } from "../../../../common/k8s-api/endpoints"; +import type { ResourceQuotaValues } from "@k8slens/kube-object"; +import type { ResourceQuotaApi } from "../../../../common/k8s-api/endpoints"; import { Select } from "../../select"; import { Icon } from "../../icon"; import { Button } from "@k8slens/button"; @@ -37,7 +38,7 @@ interface Dependencies { showCheckedErrorNotification: ShowCheckedErrorNotification; } -const defaultQuotas = JSON.stringify({ +const getDefaultQuotas = (): ResourceQuotaValues => ({ "limits.cpu": "", "limits.memory": "", "requests.cpu": "", @@ -56,7 +57,7 @@ const defaultQuotas = JSON.stringify({ "count/jobs.batch": "", "count/cronjobs.batch": "", "count/deployments.extensions": "", -} as IResourceQuotaValues); +}); @observer class NonInjectedAddQuotaDialog extends React.Component { @@ -67,7 +68,7 @@ class NonInjectedAddQuotaDialog extends React.Component !!value?.trim()); } @@ -97,7 +98,7 @@ class NonInjectedAddQuotaDialog extends React.Component { if (!this.quotaSelectValue) return; - this.quotas[this.quotaSelectValue] = this.quotaInputValue; + this.quotas.get()[this.quotaSelectValue] = this.quotaInputValue; this.quotaInputValue = ""; }; @@ -110,7 +111,7 @@ class NonInjectedAddQuotaDialog extends React.Component { @@ -130,7 +131,7 @@ class NonInjectedAddQuotaDialog extends React.ComponentCreate ResourceQuota; + void closeAddQuotaDialog; + void resourceQuotaApi; + return ( @@ -235,7 +239,7 @@ class NonInjectedAddQuotaDialog extends React.Component
{quota}
{value}
- this.quotas[quota] = ""} /> + this.quotas.get()[quota] = ""} /> ))} diff --git a/packages/core/src/renderer/components/config-resource-quotas/resource-quota-details.tsx b/packages/core/src/renderer/components/config-resource-quotas/resource-quota-details.tsx index c032de2a25..8c9463a660 100644 --- a/packages/core/src/renderer/components/config-resource-quotas/resource-quota-details.tsx +++ b/packages/core/src/renderer/components/config-resource-quotas/resource-quota-details.tsx @@ -10,7 +10,7 @@ import { observer } from "mobx-react"; import { DrawerItem, DrawerTitle } from "../drawer"; import { cpuUnitsToNumber, cssNames, unitsToBytes, metricUnitsToNumber, object, hasDefinedTupleValue } from "@k8slens/utilities"; import type { KubeObjectDetailsProps } from "../kube-object-details"; -import { ResourceQuota } from "../../../common/k8s-api/endpoints/resource-quota.api"; +import { ResourceQuota } from "@k8slens/kube-object"; import { LineProgress } from "../line-progress"; import { Table, TableCell, TableHead, TableRow } from "../table"; import type { Logger } from "../../../common/logger"; diff --git a/packages/core/src/renderer/components/config-resource-quotas/store.ts b/packages/core/src/renderer/components/config-resource-quotas/store.ts index 14272c3def..0df729b9b9 100644 --- a/packages/core/src/renderer/components/config-resource-quotas/store.ts +++ b/packages/core/src/renderer/components/config-resource-quotas/store.ts @@ -4,7 +4,8 @@ */ import { KubeObjectStore } from "../../../common/k8s-api/kube-object.store"; -import type { ResourceQuota, ResourceQuotaApi } from "../../../common/k8s-api/endpoints/resource-quota.api"; +import type { ResourceQuotaApi } from "../../../common/k8s-api/endpoints/resource-quota.api"; +import type { ResourceQuota } from "@k8slens/kube-object"; export class ResourceQuotaStore extends KubeObjectStore { } diff --git a/packages/core/src/renderer/components/config-runtime-classes/runtime-classes-details-tolerations.tsx b/packages/core/src/renderer/components/config-runtime-classes/runtime-classes-details-tolerations.tsx index c06e0c63bc..feaa8a5612 100644 --- a/packages/core/src/renderer/components/config-runtime-classes/runtime-classes-details-tolerations.tsx +++ b/packages/core/src/renderer/components/config-runtime-classes/runtime-classes-details-tolerations.tsx @@ -6,7 +6,7 @@ import "./runtime-classes-details-tolerations.scss"; import React from "react"; import { DrawerParamToggler, DrawerItem } from "../drawer"; -import type { Toleration, KubeObject } from "../../../common/k8s-api/kube-object"; +import type { Toleration, KubeObject } from "@k8slens/kube-object"; import { RuntimeClassTolerations } from "./runtime-classes-tolerations"; export interface KubeObjectWithTolerations extends KubeObject { diff --git a/packages/core/src/renderer/components/config-runtime-classes/runtime-classes-details.tsx b/packages/core/src/renderer/components/config-runtime-classes/runtime-classes-details.tsx index f6fc61faeb..3d791a559d 100644 --- a/packages/core/src/renderer/components/config-runtime-classes/runtime-classes-details.tsx +++ b/packages/core/src/renderer/components/config-runtime-classes/runtime-classes-details.tsx @@ -9,7 +9,7 @@ import React from "react"; import { observer } from "mobx-react"; import { DrawerItem } from "../drawer"; import type { KubeObjectDetailsProps } from "../kube-object-details"; -import type { RuntimeClass } from "../../../common/k8s-api/endpoints"; +import type { RuntimeClass } from "@k8slens/kube-object"; import { Badge } from "../badge"; import { RuntimeClassDetailsTolerations } from "./runtime-classes-details-tolerations"; diff --git a/packages/core/src/renderer/components/config-runtime-classes/runtime-classes-tolerations.tsx b/packages/core/src/renderer/components/config-runtime-classes/runtime-classes-tolerations.tsx index bcb1776459..98fd50dfbe 100644 --- a/packages/core/src/renderer/components/config-runtime-classes/runtime-classes-tolerations.tsx +++ b/packages/core/src/renderer/components/config-runtime-classes/runtime-classes-tolerations.tsx @@ -7,7 +7,7 @@ import "./runtime-classes-tolerations.scss"; import React from "react"; import uniqueId from "lodash/uniqueId"; -import type { Toleration } from "../../../common/k8s-api/kube-object"; +import type { Toleration } from "@k8slens/kube-object"; import { Table, TableCell, TableHead, TableRow } from "../table"; export interface RuntimeClassTolerationsProps { diff --git a/packages/core/src/renderer/components/config-runtime-classes/runtime-classes.tsx b/packages/core/src/renderer/components/config-runtime-classes/runtime-classes.tsx index d66c9da92f..e3cc559195 100644 --- a/packages/core/src/renderer/components/config-runtime-classes/runtime-classes.tsx +++ b/packages/core/src/renderer/components/config-runtime-classes/runtime-classes.tsx @@ -7,7 +7,7 @@ import "./runtime-classes.scss"; import * as React from "react"; import { observer } from "mobx-react"; -import type { RuntimeClass } from "../../../common/k8s-api/endpoints/runtime-class.api"; +import type { RuntimeClass } from "@k8slens/kube-object"; import { KubeObjectStatusIcon } from "../kube-object-status-icon"; import type { KubeObjectDetailsProps } from "../kube-object-details"; import { KubeObjectListLayout } from "../kube-object-list-layout"; diff --git a/packages/core/src/renderer/components/config-runtime-classes/store.ts b/packages/core/src/renderer/components/config-runtime-classes/store.ts index bdb849ac85..86156e24fe 100644 --- a/packages/core/src/renderer/components/config-runtime-classes/store.ts +++ b/packages/core/src/renderer/components/config-runtime-classes/store.ts @@ -4,7 +4,8 @@ */ import { KubeObjectStore } from "../../../common/k8s-api/kube-object.store"; -import type { RuntimeClass, RuntimeClassApi } from "../../../common/k8s-api/endpoints/runtime-class.api"; +import type { RuntimeClassApi } from "../../../common/k8s-api/endpoints/runtime-class.api"; +import type { RuntimeClass } from "@k8slens/kube-object"; export class RuntimeClassStore extends KubeObjectStore { } diff --git a/packages/core/src/renderer/components/config-secrets/__tests__/secret-details.test.tsx b/packages/core/src/renderer/components/config-secrets/__tests__/secret-details.test.tsx index 795c204975..82e184820f 100644 --- a/packages/core/src/renderer/components/config-secrets/__tests__/secret-details.test.tsx +++ b/packages/core/src/renderer/components/config-secrets/__tests__/secret-details.test.tsx @@ -5,7 +5,7 @@ import React from "react"; import { SecretDetails } from "../secret-details"; -import { Secret, SecretType } from "../../../../common/k8s-api/endpoints"; +import { Secret, SecretType } from "@k8slens/kube-object"; import { getDiForUnitTesting } from "../../../getDiForUnitTesting"; import { renderFor } from "../../test-utils/renderFor"; import storesAndApisCanBeCreatedInjectable from "../../../stores-apis-can-be-created.injectable"; diff --git a/packages/core/src/renderer/components/config-secrets/add-dialog/view.tsx b/packages/core/src/renderer/components/config-secrets/add-dialog/view.tsx index c977243714..8acf8226a1 100644 --- a/packages/core/src/renderer/components/config-secrets/add-dialog/view.tsx +++ b/packages/core/src/renderer/components/config-secrets/add-dialog/view.tsx @@ -15,7 +15,7 @@ import { Wizard, WizardStep } from "../../wizard"; import { Input } from "../../input"; import { systemName } from "../../input/input_validators"; import type { SecretApi } from "../../../../common/k8s-api/endpoints"; -import { reverseSecretTypeMap, SecretType } from "../../../../common/k8s-api/endpoints"; +import { reverseSecretTypeMap, SecretType } from "@k8slens/kube-object"; import { SubTitle } from "../../layout/sub-title"; import { NamespaceSelect } from "../../namespaces/namespace-select"; import { Select } from "../../select"; @@ -117,7 +117,7 @@ class NonInjectedAddSecretDialog extends React.ComponentCreate Secret; + void closeAddSecretDialog; + void secretApi; + void showDetails; + return ( { const { object: secret } = this.props; @@ -59,18 +59,20 @@ class NonInjectedSecretDetails extends React.Component { + saveSecret = () => { const { object: secret } = this.props; - this.isSaving = true; + void (async () => { + this.isSaving = true; - try { - await this.props.secretStore.update(secret, { ...secret, data: this.data }); - this.props.showSuccessNotification("Secret successfully updated."); - } catch (err) { - this.props.showCheckedErrorNotification(err, "Unknown error occured while updating the secret"); - } - this.isSaving = false; + try { + await this.props.secretStore.update(secret, { ...secret, data: this.data }); + this.props.showSuccessNotification("Secret successfully updated."); + } catch (err) { + this.props.showCheckedErrorNotification(err, "Unknown error occurred while updating the secret"); + } + this.isSaving = false; + })(); }; editData = (name: string, value: string, encoded: boolean) => { diff --git a/packages/core/src/renderer/components/config-secrets/store.ts b/packages/core/src/renderer/components/config-secrets/store.ts index f6dbaa167d..0f9162be15 100644 --- a/packages/core/src/renderer/components/config-secrets/store.ts +++ b/packages/core/src/renderer/components/config-secrets/store.ts @@ -4,7 +4,8 @@ */ import { KubeObjectStore } from "../../../common/k8s-api/kube-object.store"; -import type { Secret, SecretApi, SecretData } from "../../../common/k8s-api/endpoints"; +import type { SecretApi } from "../../../common/k8s-api/endpoints"; +import type { Secret, SecretData } from "@k8slens/kube-object"; export class SecretStore extends KubeObjectStore { } diff --git a/packages/core/src/renderer/components/config-validating-webhook-configurations/details.test.tsx b/packages/core/src/renderer/components/config-validating-webhook-configurations/details.test.tsx index 3ccb7e0725..bd86b8dda6 100644 --- a/packages/core/src/renderer/components/config-validating-webhook-configurations/details.test.tsx +++ b/packages/core/src/renderer/components/config-validating-webhook-configurations/details.test.tsx @@ -4,15 +4,14 @@ */ import type { RenderResult } from "@testing-library/react"; import React from "react"; -import { - ValidatingWebhookConfiguration, -} from "../../../common/k8s-api/endpoints"; +import type { ValidatingWebhookConfigurationData } from "@k8slens/kube-object"; +import { ValidatingWebhookConfiguration } from "@k8slens/kube-object"; import { getDiForUnitTesting } from "../../getDiForUnitTesting"; import type { DiRender } from "../test-utils/renderFor"; import { renderFor } from "../test-utils/renderFor"; import { ValidatingWebhookDetails } from "./validating-webhook-configurations-details"; -const validatingWebhookConfig = { +const validatingWebhookConfig: ValidatingWebhookConfigurationData = { apiVersion: "admissionregistration.k8s.io/v1", kind: "ValidatingWebhookConfiguration", metadata: { @@ -52,7 +51,7 @@ describe("ValidatingWebhookConfigsDetails", () => { let result: RenderResult; let render: DiRender; - beforeEach(async () => { + beforeEach(() => { const di = getDiForUnitTesting(); render = renderFor(di); diff --git a/packages/core/src/renderer/components/config-validating-webhook-configurations/validating-webhook-configuration-store.ts b/packages/core/src/renderer/components/config-validating-webhook-configurations/validating-webhook-configuration-store.ts index b3e6eb2d66..be34506072 100644 --- a/packages/core/src/renderer/components/config-validating-webhook-configurations/validating-webhook-configuration-store.ts +++ b/packages/core/src/renderer/components/config-validating-webhook-configurations/validating-webhook-configuration-store.ts @@ -2,21 +2,9 @@ * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ -import type { - ValidatingWebhookConfiguration, - ValidatingWebhookConfigurationApi, -} from "../../../common/k8s-api/endpoints"; -import type { - KubeObjectStoreDependencies, - KubeObjectStoreOptions, -} from "../../../common/k8s-api/kube-object.store"; +import type { ValidatingWebhookConfiguration } from "@k8slens/kube-object"; +import type { ValidatingWebhookConfigurationApi } from "../../../common/k8s-api/endpoints"; import { KubeObjectStore } from "../../../common/k8s-api/kube-object.store"; -export interface ValidatingWebhookConfigurationStoreDependencies extends KubeObjectStoreDependencies { -} - export class ValidatingWebhookConfigurationStore extends KubeObjectStore { - constructor(protected readonly dependencies: ValidatingWebhookConfigurationStoreDependencies, api: ValidatingWebhookConfigurationApi, opts?: KubeObjectStoreOptions) { - super(dependencies, api, opts); - } } diff --git a/packages/core/src/renderer/components/config-validating-webhook-configurations/validating-webhook-configurations-details.tsx b/packages/core/src/renderer/components/config-validating-webhook-configurations/validating-webhook-configurations-details.tsx index 5622746a86..63c83161ed 100644 --- a/packages/core/src/renderer/components/config-validating-webhook-configurations/validating-webhook-configurations-details.tsx +++ b/packages/core/src/renderer/components/config-validating-webhook-configurations/validating-webhook-configurations-details.tsx @@ -7,7 +7,7 @@ import { observer } from "mobx-react"; import { DrawerItem, DrawerTitle } from "../drawer"; import type { KubeObjectDetailsProps } from "../kube-object-details"; import { WebhookConfig } from "../config-mutating-webhook-configurations/webhook-config"; -import type { ValidatingWebhookConfiguration } from "../../../common/k8s-api/endpoints"; +import type { ValidatingWebhookConfiguration } from "@k8slens/kube-object"; export interface ValidatingWebhookProps extends KubeObjectDetailsProps { } diff --git a/packages/core/src/renderer/components/config-vertical-pod-autoscalers/store.ts b/packages/core/src/renderer/components/config-vertical-pod-autoscalers/store.ts index edb8efbdd0..bf475f5c0c 100644 --- a/packages/core/src/renderer/components/config-vertical-pod-autoscalers/store.ts +++ b/packages/core/src/renderer/components/config-vertical-pod-autoscalers/store.ts @@ -4,7 +4,8 @@ */ import { KubeObjectStore } from "../../../common/k8s-api/kube-object.store"; -import type { VerticalPodAutoscaler, VerticalPodAutoscalerApi } from "../../../common/k8s-api/endpoints/vertical-pod-autoscaler.api"; +import type { VerticalPodAutoscalerApi } from "../../../common/k8s-api/endpoints/vertical-pod-autoscaler.api"; +import type { VerticalPodAutoscaler } from "@k8slens/kube-object"; export class VerticalPodAutoscalerStore extends KubeObjectStore { } diff --git a/packages/core/src/renderer/components/config-vertical-pod-autoscalers/vpa-details.tsx b/packages/core/src/renderer/components/config-vertical-pod-autoscalers/vpa-details.tsx index 532ecf39aa..51b703aa20 100644 --- a/packages/core/src/renderer/components/config-vertical-pod-autoscalers/vpa-details.tsx +++ b/packages/core/src/renderer/components/config-vertical-pod-autoscalers/vpa-details.tsx @@ -13,8 +13,8 @@ import { DrawerItem, DrawerTitle } from "../drawer"; import { Badge } from "../badge"; import type { KubeObjectDetailsProps } from "../kube-object-details"; import { cssNames } from "@k8slens/utilities"; -import { ContainerScalingMode, ControlledValues, ResourceName, UpdateMode, VerticalPodAutoscaler } from "../../../common/k8s-api/endpoints/vertical-pod-autoscaler.api"; -import type { PodUpdatePolicy, PodResourcePolicy, VerticalPodAutoscalerStatus } from "../../../common/k8s-api/endpoints/vertical-pod-autoscaler.api"; +import { ContainerScalingMode, ControlledValues, ResourceName, UpdateMode, VerticalPodAutoscaler } from "@k8slens/kube-object"; +import type { PodUpdatePolicy, PodResourcePolicy, VerticalPodAutoscalerStatus } from "@k8slens/kube-object"; import type { ApiManager } from "../../../common/k8s-api/api-manager"; import loggerInjectable from "../../../common/logger.injectable"; import type { Logger } from "../../../common/logger"; @@ -59,47 +59,45 @@ class NonInjectedVpaDetails extends React.Component { - return ( -
- {`Container Recommendation for ${containerName}`} - - {Object.entries(target).map(([name, value]) => ( + .map( ({ containerName, target, lowerBound, upperBound, uncappedTarget }) => ( +
+ {`Container Recommendation for ${containerName ?? ""}`} + + {Object.entries(target).map(([name, value]) => ( + + {value} + + ))} + + {lowerBound && ( + + {Object.entries(lowerBound).map(([name, value]) => ( {value} ))} - {lowerBound && ( - - {Object.entries(lowerBound).map(([name, value]) => ( - - {value} - - ))} - - )} - {upperBound && ( - - {Object.entries(upperBound).map(([name, value]) => ( - - {value} - - ))} - - )} - {uncappedTarget && ( - - {Object.entries(uncappedTarget).map(([name, value]) => ( - - {value} - - ))} - - )} -
- ); - }) + )} + {upperBound && ( + + {Object.entries(upperBound).map(([name, value]) => ( + + {value} + + ))} + + )} + {uncappedTarget && ( + + {Object.entries(uncappedTarget).map(([name, value]) => ( + + {value} + + ))} + + )} +
+ )) )} ); @@ -128,7 +126,7 @@ class NonInjectedVpaDetails extends React.Component { return (
- {`Container Policy for ${containerName}`} + {`Container Policy for ${containerName ?? ""}`} {mode ?? ContainerScalingMode.ContainerScalingModeAuto} diff --git a/packages/core/src/renderer/components/confirm-dialog/confirm-dialog.tsx b/packages/core/src/renderer/components/confirm-dialog/confirm-dialog.tsx index 9494b17ee7..1ebecfe321 100644 --- a/packages/core/src/renderer/components/confirm-dialog/confirm-dialog.tsx +++ b/packages/core/src/renderer/components/confirm-dialog/confirm-dialog.tsx @@ -79,7 +79,7 @@ class NonInjectedConfirmDialog extends React.Component , @@ -107,7 +107,7 @@ class NonInjectedConfirmDialog extends React.Component , diff --git a/packages/core/src/renderer/components/custom-resources/__tests__/custom-resource-details.test.tsx b/packages/core/src/renderer/components/custom-resources/__tests__/custom-resource-details.test.tsx index 7da0a91994..d9dfe53423 100644 --- a/packages/core/src/renderer/components/custom-resources/__tests__/custom-resource-details.test.tsx +++ b/packages/core/src/renderer/components/custom-resources/__tests__/custom-resource-details.test.tsx @@ -4,8 +4,8 @@ */ import React from "react"; -import { CustomResourceDefinition } from "../../../../common/k8s-api/endpoints"; -import { KubeObject } from "../../../../common/k8s-api/kube-object"; +import { CustomResourceDefinition } from "@k8slens/kube-object"; +import { KubeObject } from "@k8slens/kube-object"; import { getDiForUnitTesting } from "../../../getDiForUnitTesting"; import type { DiRender } from "../../test-utils/renderFor"; import { renderFor } from "../../test-utils/renderFor"; diff --git a/packages/core/src/renderer/components/custom-resources/crd-details.tsx b/packages/core/src/renderer/components/custom-resources/crd-details.tsx index 23194dbc73..2b2eed0198 100644 --- a/packages/core/src/renderer/components/custom-resources/crd-details.tsx +++ b/packages/core/src/renderer/components/custom-resources/crd-details.tsx @@ -8,7 +8,7 @@ import "./crd-details.scss"; import React from "react"; import { Link } from "react-router-dom"; import { observer } from "mobx-react"; -import { CustomResourceDefinition } from "../../../common/k8s-api/endpoints/custom-resource-definition.api"; +import { CustomResourceDefinition } from "@k8slens/kube-object"; import { Badge } from "../badge"; import { DrawerItem, DrawerTitle } from "../drawer"; import type { KubeObjectDetailsProps } from "../kube-object-details"; diff --git a/packages/core/src/renderer/components/custom-resources/crd-resource-details.tsx b/packages/core/src/renderer/components/custom-resources/crd-resource-details.tsx index 6b899563a9..e428e3e336 100644 --- a/packages/core/src/renderer/components/custom-resources/crd-resource-details.tsx +++ b/packages/core/src/renderer/components/custom-resources/crd-resource-details.tsx @@ -12,10 +12,8 @@ import { Badge } from "../badge"; import { DrawerItem } from "../drawer"; import type { KubeObjectDetailsProps } from "../kube-object-details"; import { Input } from "../input"; -import type { AdditionalPrinterColumnsV1 } from "../../../common/k8s-api/endpoints/custom-resource-definition.api"; -import { CustomResourceDefinition } from "../../../common/k8s-api/endpoints/custom-resource-definition.api"; -import type { KubeObjectMetadata, KubeObjectStatus } from "../../../common/k8s-api/kube-object"; -import { KubeObject } from "../../../common/k8s-api/kube-object"; +import type { AdditionalPrinterColumnsV1, KubeObjectMetadata, KubeObjectStatus } from "@k8slens/kube-object"; +import { CustomResourceDefinition, KubeObject } from "@k8slens/kube-object"; import type { Logger } from "../../../common/logger"; import { withInjectables } from "@ogre-tools/injectable-react"; import loggerInjectable from "../../../common/logger.injectable"; @@ -91,7 +89,7 @@ class NonInjectedCustomResourceDetails extends React.Component ( { constructor( diff --git a/packages/core/src/renderer/components/dock/create-resource/view.tsx b/packages/core/src/renderer/components/dock/create-resource/view.tsx index 73d6989b82..55c29b7642 100644 --- a/packages/core/src/renderer/components/dock/create-resource/view.tsx +++ b/packages/core/src/renderer/components/dock/create-resource/view.tsx @@ -96,7 +96,7 @@ class NonInjectedCreateResource extends React.Component { const logs = await callForLogs(params, query).catch(error => { logger.error("Can't download logs: ", error); diff --git a/packages/core/src/renderer/components/dock/logs/load-logs.injectable.ts b/packages/core/src/renderer/components/dock/logs/load-logs.injectable.ts index 0d1b72ed30..34c7a41749 100644 --- a/packages/core/src/renderer/components/dock/logs/load-logs.injectable.ts +++ b/packages/core/src/renderer/components/dock/logs/load-logs.injectable.ts @@ -4,7 +4,7 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import type { IComputedValue } from "mobx"; -import type { Pod } from "../../../../common/k8s-api/endpoints"; +import type { Pod } from "@k8slens/kube-object"; import logStoreInjectable from "./store.injectable"; import type { LogTabData } from "./tab-store"; diff --git a/packages/core/src/renderer/components/dock/logs/logs-view-model.ts b/packages/core/src/renderer/components/dock/logs/logs-view-model.ts index a36235cfd0..7df6bb3745 100644 --- a/packages/core/src/renderer/components/dock/logs/logs-view-model.ts +++ b/packages/core/src/renderer/components/dock/logs/logs-view-model.ts @@ -7,7 +7,7 @@ import type { IComputedValue } from "mobx"; import { computed } from "mobx"; import type { TabId } from "../dock/store"; import type { SearchStore } from "../../../search-store/search-store"; -import type { Pod, PodLogsQuery } from "../../../../common/k8s-api/endpoints"; +import type { Pod, PodLogsQuery } from "@k8slens/kube-object"; import { isDefined } from "@k8slens/utilities"; import assert from "assert"; import type { GetPodById } from "../../workloads-pods/get-pod-by-id.injectable"; diff --git a/packages/core/src/renderer/components/dock/logs/reload-logs.injectable.ts b/packages/core/src/renderer/components/dock/logs/reload-logs.injectable.ts index ef2e878c87..984efe71db 100644 --- a/packages/core/src/renderer/components/dock/logs/reload-logs.injectable.ts +++ b/packages/core/src/renderer/components/dock/logs/reload-logs.injectable.ts @@ -4,7 +4,7 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import type { IComputedValue } from "mobx"; -import type { Pod } from "../../../../common/k8s-api/endpoints"; +import type { Pod } from "@k8slens/kube-object"; import logStoreInjectable from "./store.injectable"; import type { LogTabData } from "./tab-store"; diff --git a/packages/core/src/renderer/components/dock/logs/resource-selector.tsx b/packages/core/src/renderer/components/dock/logs/resource-selector.tsx index ddca1b2c54..0d20a0561d 100644 --- a/packages/core/src/renderer/components/dock/logs/resource-selector.tsx +++ b/packages/core/src/renderer/components/dock/logs/resource-selector.tsx @@ -12,7 +12,7 @@ import { Badge } from "../../badge"; import type { SelectOption } from "../../select"; import { Select } from "../../select"; import type { LogTabViewModel } from "./logs-view-model"; -import type { Container, Pod } from "../../../../common/k8s-api/endpoints"; +import type { Container, Pod } from "@k8slens/kube-object"; import type { SingleValue } from "react-select"; export interface LogResourceSelectorProps { diff --git a/packages/core/src/renderer/components/dock/logs/store.ts b/packages/core/src/renderer/components/dock/logs/store.ts index 0809135f4f..f6bdaeb3c9 100644 --- a/packages/core/src/renderer/components/dock/logs/store.ts +++ b/packages/core/src/renderer/components/dock/logs/store.ts @@ -5,7 +5,7 @@ import type { IComputedValue } from "mobx"; import { observable } from "mobx"; -import type { PodLogsQuery, Pod } from "../../../../common/k8s-api/endpoints"; +import type { PodLogsQuery, Pod } from "@k8slens/kube-object"; import { waitUntilDefined, getOrInsertWith, interval } from "@k8slens/utilities"; import type { IntervalFn } from "@k8slens/utilities"; import type { TabId } from "../dock/store"; diff --git a/packages/core/src/renderer/components/drawer/drawer-item-labels.tsx b/packages/core/src/renderer/components/drawer/drawer-item-labels.tsx index 861feb52b9..cb76d4ed97 100644 --- a/packages/core/src/renderer/components/drawer/drawer-item-labels.tsx +++ b/packages/core/src/renderer/components/drawer/drawer-item-labels.tsx @@ -7,7 +7,7 @@ import React from "react"; import type { DrawerItemProps } from "./drawer-item"; import { DrawerItem } from "./drawer-item"; import { Badge } from "../badge"; -import { KubeObject } from "../../../common/k8s-api/kube-object"; +import { KubeObject } from "@k8slens/kube-object"; export interface DrawerItemLabelsProps extends DrawerItemProps { labels: string[] | Partial>; diff --git a/packages/core/src/renderer/components/events/event-details.tsx b/packages/core/src/renderer/components/events/event-details.tsx index 1e825993c4..b6987d10f2 100644 --- a/packages/core/src/renderer/components/events/event-details.tsx +++ b/packages/core/src/renderer/components/events/event-details.tsx @@ -11,7 +11,7 @@ import { DrawerItem, DrawerTitle } from "../drawer"; import { Link } from "react-router-dom"; import { observer } from "mobx-react"; import type { KubeObjectDetailsProps } from "../kube-object-details"; -import { KubeEvent } from "../../../common/k8s-api/endpoints/events.api"; +import { KubeEvent } from "@k8slens/kube-object"; import { Table, TableCell, TableHead, TableRow } from "../table"; import type { ApiManager } from "../../../common/k8s-api/api-manager"; import type { Logger } from "../../../common/logger"; diff --git a/packages/core/src/renderer/components/events/events.tsx b/packages/core/src/renderer/components/events/events.tsx index d081881c93..bf8b1e3215 100644 --- a/packages/core/src/renderer/components/events/events.tsx +++ b/packages/core/src/renderer/components/events/events.tsx @@ -13,8 +13,8 @@ import { TabLayout } from "../layout/tab-layout-2"; import type { EventStore } from "./store"; import type { KubeObjectListLayoutProps } from "../kube-object-list-layout"; import { KubeObjectListLayout } from "../kube-object-list-layout"; -import type { KubeEvent, KubeEventApi, KubeEventData } from "../../../common/k8s-api/endpoints/events.api"; -import type { TableSortCallbacks, TableSortParams } from "../table"; +import type { KubeEvent, KubeEventData } from "@k8slens/kube-object"; +import type { TableSortParams, TableSortCallbacks } from "../table"; import type { HeaderCustomizer } from "../item-object-list"; import { Tooltip } from "@k8slens/tooltip"; import { Link } from "react-router-dom"; @@ -31,6 +31,7 @@ import eventStoreInjectable from "./store.injectable"; import type { GetDetailsUrl } from "../kube-detail-params/get-details-url.injectable"; import getDetailsUrlInjectable from "../kube-detail-params/get-details-url.injectable"; import { NamespaceSelectBadge } from "../namespaces/namespace-select-badge"; +import type { KubeEventApi } from "../../../common/k8s-api/endpoints"; enum columnId { message = "message", @@ -64,10 +65,10 @@ interface Dependencies { class NonInjectedEvents extends React.Component { static defaultProps = defaultProps as object; - @observable sorting: TableSortParams = { + readonly sorting = observable.object({ sortBy: columnId.age, orderBy: "asc", - }; + }); private sortingCallbacks: TableSortCallbacks = { [columnId.namespace]: event => event.getNs(), @@ -162,7 +163,7 @@ class NonInjectedEvents extends React.Component { tableProps={{ sortSyncWithUrl: false, sortByDefault: this.sorting, - onSort: params => this.sorting = params, + onSort: params => Object.assign(this.sorting, params), }} sortingCallbacks={this.sortingCallbacks} searchFilters={[ diff --git a/packages/core/src/renderer/components/events/kube-event-details.tsx b/packages/core/src/renderer/components/events/kube-event-details.tsx index 76010a8e14..b9e552a23a 100644 --- a/packages/core/src/renderer/components/events/kube-event-details.tsx +++ b/packages/core/src/renderer/components/events/kube-event-details.tsx @@ -7,7 +7,7 @@ import styles from "./kube-event-details.module.scss"; import React from "react"; import { disposeOnUnmount, observer } from "mobx-react"; -import { KubeObject } from "../../../common/k8s-api/kube-object"; +import { KubeObject } from "@k8slens/kube-object"; import { DrawerItem, DrawerTitle } from "../drawer"; import { cssNames } from "@k8slens/utilities"; import { LocaleDate } from "../locale-date"; diff --git a/packages/core/src/renderer/components/events/kube-event-icon.tsx b/packages/core/src/renderer/components/events/kube-event-icon.tsx index 7a68d951b4..76432de753 100644 --- a/packages/core/src/renderer/components/events/kube-event-icon.tsx +++ b/packages/core/src/renderer/components/events/kube-event-icon.tsx @@ -7,9 +7,8 @@ import "./kube-event-icon.scss"; import React from "react"; import { Icon } from "../icon"; -import type { KubeObject } from "../../../common/k8s-api/kube-object"; +import type { KubeObject, KubeEvent } from "@k8slens/kube-object"; import { cssNames } from "@k8slens/utilities"; -import type { KubeEvent } from "../../../common/k8s-api/endpoints/events.api"; import { KubeObjectAge } from "../kube-object/age"; import type { EventStore } from "./store"; import { withInjectables } from "@ogre-tools/injectable-react"; diff --git a/packages/core/src/renderer/components/events/store.ts b/packages/core/src/renderer/components/events/store.ts index ac0e875cc6..8ec4301e4c 100644 --- a/packages/core/src/renderer/components/events/store.ts +++ b/packages/core/src/renderer/components/events/store.ts @@ -7,17 +7,19 @@ import groupBy from "lodash/groupBy"; import compact from "lodash/compact"; import type { KubeObjectStoreDependencies, KubeObjectStoreOptions } from "../../../common/k8s-api/kube-object.store"; import { KubeObjectStore } from "../../../common/k8s-api/kube-object.store"; -import type { KubeEvent, KubeEventApi } from "../../../common/k8s-api/endpoints/events.api"; -import type { KubeObject } from "../../../common/k8s-api/kube-object"; -import { Pod } from "../../../common/k8s-api/endpoints/pod.api"; +import type { KubeEvent, KubeObject } from "@k8slens/kube-object"; +import { Pod } from "@k8slens/kube-object"; import type { GetPodById } from "../workloads-pods/get-pod-by-id.injectable"; import autoBind from "auto-bind"; +import type { KubeEventApi } from "../../../common/k8s-api/endpoints"; export interface EventStoreDependencies extends KubeObjectStoreDependencies { getPodById: GetPodById; } export class EventStore extends KubeObjectStore { + declare public readonly limit: number; + constructor( protected readonly dependencies: EventStoreDependencies, api: KubeEventApi, diff --git a/packages/core/src/renderer/components/helm-releases/dialog/dialog.tsx b/packages/core/src/renderer/components/helm-releases/dialog/dialog.tsx index 1fa6370cbc..c164bdd02a 100644 --- a/packages/core/src/renderer/components/helm-releases/dialog/dialog.tsx +++ b/packages/core/src/renderer/components/helm-releases/dialog/dialog.tsx @@ -73,7 +73,7 @@ class NonInjectedReleaseRollbackDialog extends React.Component diff --git a/packages/core/src/renderer/components/kube-object-details/kube-object-detail-registration.ts b/packages/core/src/renderer/components/kube-object-details/kube-object-detail-registration.ts index c755630a80..9164da28c9 100644 --- a/packages/core/src/renderer/components/kube-object-details/kube-object-detail-registration.ts +++ b/packages/core/src/renderer/components/kube-object-details/kube-object-detail-registration.ts @@ -2,7 +2,7 @@ * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ -import type { KubeObject } from "../../../common/k8s-api/kube-object"; +import type { KubeObject } from "@k8slens/kube-object"; import type { KubeObjectDetailsProps } from "./kube-object-details"; import type React from "react"; import type { IComputedValue } from "mobx"; diff --git a/packages/core/src/renderer/components/kube-object-details/kube-object-details.tsx b/packages/core/src/renderer/components/kube-object-details/kube-object-details.tsx index 26f9ab7568..bf6c6566f7 100644 --- a/packages/core/src/renderer/components/kube-object-details/kube-object-details.tsx +++ b/packages/core/src/renderer/components/kube-object-details/kube-object-details.tsx @@ -9,7 +9,7 @@ import React from "react"; import { observer } from "mobx-react"; import type { IComputedValue } from "mobx"; import { Drawer } from "../drawer"; -import type { KubeObject } from "../../../common/k8s-api/kube-object"; +import type { KubeObject } from "@k8slens/kube-object"; import { Spinner } from "../spinner"; import { KubeObjectMenu } from "../kube-object-menu"; import type { HideDetails } from "../kube-detail-params/hide-details.injectable"; diff --git a/packages/core/src/renderer/components/kube-object-list-layout/kube-object-list-layout.tsx b/packages/core/src/renderer/components/kube-object-list-layout/kube-object-list-layout.tsx index e9183049ab..5b38a61ac8 100644 --- a/packages/core/src/renderer/components/kube-object-list-layout/kube-object-list-layout.tsx +++ b/packages/core/src/renderer/components/kube-object-list-layout/kube-object-list-layout.tsx @@ -10,7 +10,7 @@ import { computed, observable, reaction } from "mobx"; import { disposeOnUnmount, observer } from "mobx-react"; import type { Disposer } from "@k8slens/utilities"; import { hasTypedProperty, isObject, isString, cssNames, isDefined } from "@k8slens/utilities"; -import type { KubeJsonApiDataFor, KubeObject } from "../../../common/k8s-api/kube-object"; +import type { KubeJsonApiDataFor, KubeObject } from "@k8slens/kube-object"; import type { ItemListLayoutProps, ItemListStore } from "../item-object-list/list-layout"; import { ItemListLayout } from "../item-object-list/list-layout"; import { KubeObjectMenu } from "../kube-object-menu"; @@ -39,7 +39,7 @@ export type KubeItemListStore = ItemListStore & export interface KubeObjectListLayoutProps< K extends KubeObject, - // eslint-disable-next-line unused-imports/no-unused-vars-ts + // eslint-disable-next-line unused-imports/no-unused-vars-ts, @typescript-eslint/no-unused-vars A extends KubeApi, D extends KubeJsonApiDataFor, > extends Omit, "getItems" | "dependentStores" | "preloadStores"> { diff --git a/packages/core/src/renderer/components/kube-object-menu/kube-object-menu-item-injection-token.ts b/packages/core/src/renderer/components/kube-object-menu/kube-object-menu-item-injection-token.ts index a3ec5dba4b..6e17c4a74c 100644 --- a/packages/core/src/renderer/components/kube-object-menu/kube-object-menu-item-injection-token.ts +++ b/packages/core/src/renderer/components/kube-object-menu/kube-object-menu-item-injection-token.ts @@ -5,7 +5,7 @@ import { getInjectionToken } from "@ogre-tools/injectable"; import type { IComputedValue } from "mobx"; import type { KubeObjectMenuProps } from "./kube-object-menu"; -import type { KubeObject } from "../../../common/k8s-api/kube-object"; +import type { KubeObject } from "@k8slens/kube-object"; import type React from "react"; export type KubeObjectMenuItemComponent = React.ElementType< diff --git a/packages/core/src/renderer/components/kube-object-menu/kube-object-menu-items.injectable.ts b/packages/core/src/renderer/components/kube-object-menu/kube-object-menu-items.injectable.ts index 796a805dfd..813f9846f6 100644 --- a/packages/core/src/renderer/components/kube-object-menu/kube-object-menu-items.injectable.ts +++ b/packages/core/src/renderer/components/kube-object-menu/kube-object-menu-items.injectable.ts @@ -4,7 +4,7 @@ */ import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable"; -import type { KubeObject } from "../../../common/k8s-api/kube-object"; +import type { KubeObject } from "@k8slens/kube-object"; import { computed } from "mobx"; import { computedInjectManyInjectable } from "@ogre-tools/injectable-extension-for-mobx"; import { kubeObjectMenuItemInjectionToken } from "./kube-object-menu-item-injection-token"; diff --git a/packages/core/src/renderer/components/kube-object-menu/kube-object-menu-registration.ts b/packages/core/src/renderer/components/kube-object-menu/kube-object-menu-registration.ts index 939fea89f0..ab574e3fda 100644 --- a/packages/core/src/renderer/components/kube-object-menu/kube-object-menu-registration.ts +++ b/packages/core/src/renderer/components/kube-object-menu/kube-object-menu-registration.ts @@ -5,7 +5,7 @@ import type { IComputedValue } from "mobx"; import type React from "react"; -import type { KubeObject } from "../../../common/k8s-api/kube-object"; +import type { KubeObject } from "@k8slens/kube-object"; export interface KubeObjectMenuItemProps { object: KubeObject; diff --git a/packages/core/src/renderer/components/kube-object-menu/kube-object-menu.test.tsx b/packages/core/src/renderer/components/kube-object-menu/kube-object-menu.test.tsx index 347504b48b..975cfc50f3 100644 --- a/packages/core/src/renderer/components/kube-object-menu/kube-object-menu.test.tsx +++ b/packages/core/src/renderer/components/kube-object-menu/kube-object-menu.test.tsx @@ -6,7 +6,7 @@ import React from "react"; import type { RenderResult } from "@testing-library/react"; import { screen, waitFor } from "@testing-library/react"; import "@testing-library/jest-dom/extend-expect"; -import { KubeObject } from "../../../common/k8s-api/kube-object"; +import { KubeObject } from "@k8slens/kube-object"; import userEvent from "@testing-library/user-event"; import { getInjectable } from "@ogre-tools/injectable"; import type { DiContainer } from "@ogre-tools/injectable"; diff --git a/packages/core/src/renderer/components/kube-object-menu/kube-object-menu.tsx b/packages/core/src/renderer/components/kube-object-menu/kube-object-menu.tsx index 6e6037f2ac..423d8acb33 100644 --- a/packages/core/src/renderer/components/kube-object-menu/kube-object-menu.tsx +++ b/packages/core/src/renderer/components/kube-object-menu/kube-object-menu.tsx @@ -5,7 +5,7 @@ import React from "react"; import { cssNames } from "@k8slens/utilities"; -import type { KubeObject } from "../../../common/k8s-api/kube-object"; +import type { KubeObject } from "@k8slens/kube-object"; import type { MenuActionsProps } from "../menu"; import { MenuItem, MenuActions } from "../menu"; import identity from "lodash/identity"; diff --git a/packages/core/src/renderer/components/kube-object-menu/on-context-menu-open.injectable.ts b/packages/core/src/renderer/components/kube-object-menu/on-context-menu-open.injectable.ts index 96f91c9baa..b97c52a710 100644 --- a/packages/core/src/renderer/components/kube-object-menu/on-context-menu-open.injectable.ts +++ b/packages/core/src/renderer/components/kube-object-menu/on-context-menu-open.injectable.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import type { KubeObject } from "../../../common/k8s-api/kube-object"; +import type { KubeObject } from "@k8slens/kube-object"; import type { KubeObjectOnContextMenuOpenContext } from "../../kube-object/handler"; import kubeObjectHandlersInjectable from "../../kube-object/handlers.injectable"; diff --git a/packages/core/src/renderer/components/kube-object-meta/kube-object-meta.tsx b/packages/core/src/renderer/components/kube-object-meta/kube-object-meta.tsx index b0ad4bac1a..d71344cbf3 100644 --- a/packages/core/src/renderer/components/kube-object-meta/kube-object-meta.tsx +++ b/packages/core/src/renderer/components/kube-object-meta/kube-object-meta.tsx @@ -4,8 +4,8 @@ */ import React from "react"; -import type { KubeMetaField } from "../../../common/k8s-api/kube-object"; -import { KubeObject } from "../../../common/k8s-api/kube-object"; +import type { KubeMetaField } from "@k8slens/kube-object"; +import { KubeObject } from "@k8slens/kube-object"; import { DrawerItem, DrawerItemLabels } from "../drawer"; import type { ApiManager } from "../../../common/k8s-api/api-manager"; import { Link } from "react-router-dom"; @@ -34,8 +34,8 @@ interface Dependencies { logger: Logger; } -const NonInjectedKubeObjectMeta = observer(( - { +const NonInjectedKubeObjectMeta = observer((props : Dependencies & KubeObjectMetaProps) => { + const { apiManager, getDetailsUrl, object, @@ -46,8 +46,8 @@ const NonInjectedKubeObjectMeta = observer(( ], logger, namespaceApi, - } - : Dependencies & KubeObjectMetaProps) => { + } = props; + if (!object) { return null; } @@ -60,15 +60,12 @@ const NonInjectedKubeObjectMeta = observer(( const isHidden = (field: KubeMetaField) => hideFields.includes(field); - const { - getNs, getLabels, getResourceVersion, selfLink, getAnnotations, - getFinalizers, getId, getName, metadata: { creationTimestamp }, - } = object; + const { selfLink, metadata: { creationTimestamp }} = object; const ownerRefs = object.getOwnerRefs(); - const namespace = getNs(); - const namespaceDetailsUrl = namespace ? getDetailsUrl( - namespaceApi.formatUrlForNotListing({ name: namespace }), - ) : ""; + const namespace = object.getNs(); + const namespaceDetailsUrl = namespace + ? getDetailsUrl(namespaceApi.formatUrlForNotListing({ name: namespace })) + : ""; return ( <> @@ -78,34 +75,34 @@ const NonInjectedKubeObjectMeta = observer(( {creationTimestamp && }
@@ -519,7 +519,7 @@ exports[` given cluster with list nodes and namespaces permissio
- +
given cluster without list nodes, but with namespaces
- +
{ ) : (
- An error has occured. No route can be found matching the + An error has occurred. No route can be found matching the current route, which is also the starting route.
diff --git a/packages/core/src/renderer/frames/cluster-frame/cluster-frame.test.tsx b/packages/core/src/renderer/frames/cluster-frame/cluster-frame.test.tsx index 791a6b6a3d..7660888af1 100644 --- a/packages/core/src/renderer/frames/cluster-frame/cluster-frame.test.tsx +++ b/packages/core/src/renderer/frames/cluster-frame/cluster-frame.test.tsx @@ -92,7 +92,7 @@ describe("", () => { const result = render(); expect( - result.getByText("An error has occured. No route can be found matching the current route, which is also the starting route."), + result.getByText("An error has occurred. No route can be found matching the current route, which is also the starting route."), ).toBeInTheDocument(); }); }); diff --git a/packages/core/src/renderer/kube-object/handler.ts b/packages/core/src/renderer/kube-object/handler.ts index 63d7ccd9fe..7130dda2f0 100644 --- a/packages/core/src/renderer/kube-object/handler.ts +++ b/packages/core/src/renderer/kube-object/handler.ts @@ -6,7 +6,7 @@ import { getInjectionToken } from "@ogre-tools/injectable"; import type { IObservableArray } from "mobx"; import type { RequireAtLeastOne } from "type-fest"; -import type { KubeObject } from "../../common/k8s-api/kube-object"; +import type { KubeObject } from "@k8slens/kube-object"; import type { BaseIconProps } from "../components/icon"; export interface KubeObjectContextMenuItem { diff --git a/packages/core/src/renderer/kube-object/handlers/stateful-set.injectable.ts b/packages/core/src/renderer/kube-object/handlers/stateful-set.injectable.ts index a24fd3a298..56e685d9af 100644 --- a/packages/core/src/renderer/kube-object/handlers/stateful-set.injectable.ts +++ b/packages/core/src/renderer/kube-object/handlers/stateful-set.injectable.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import type { StatefulSet } from "../../../common/k8s-api/endpoints"; +import type { StatefulSet } from "@k8slens/kube-object"; import openStatefulSetScaleDialogInjectable from "../../components/workloads-statefulsets/scale/open-dialog.injectable"; import { staticKubeObjectHandlerInjectionToken } from "../handler"; diff --git a/packages/kube-object/.eslintrc.js b/packages/kube-object/.eslintrc.js new file mode 100644 index 0000000000..19a14e85a4 --- /dev/null +++ b/packages/kube-object/.eslintrc.js @@ -0,0 +1,6 @@ +module.exports = { + extends: "@k8slens/eslint-config/eslint", + parserOptions: { + project: "./tsconfig.json", + }, +}; \ No newline at end of file diff --git a/packages/kube-object/.prettierrc b/packages/kube-object/.prettierrc new file mode 100644 index 0000000000..edd47b479e --- /dev/null +++ b/packages/kube-object/.prettierrc @@ -0,0 +1 @@ +"@k8slens/eslint-config/prettier" diff --git a/packages/kube-object/index.ts b/packages/kube-object/index.ts new file mode 100644 index 0000000000..dd29aef599 --- /dev/null +++ b/packages/kube-object/index.ts @@ -0,0 +1,11 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +export * from "./src/api-types"; +export * from "./src/kube-object"; +export * from "./src/kube-status"; +export * from "./src/specifics"; +export * from "./src/types"; +export * from "./src/utils"; diff --git a/packages/kube-object/jest.config.js b/packages/kube-object/jest.config.js new file mode 100644 index 0000000000..38d54ab7b6 --- /dev/null +++ b/packages/kube-object/jest.config.js @@ -0,0 +1 @@ +module.exports = require("@k8slens/jest").monorepoPackageConfig(__dirname).configForReact; diff --git a/packages/kube-object/package.json b/packages/kube-object/package.json new file mode 100644 index 0000000000..e89982f289 --- /dev/null +++ b/packages/kube-object/package.json @@ -0,0 +1,49 @@ +{ + "name": "@k8slens/kube-object", + "private": false, + "version": "1.0.0-alpha.1", + "description": "Injection tokens for list layout", + "type": "commonjs", + "publishConfig": { + "access": "public", + "registry": "https://registry.npmjs.org/" + }, + "files": [ + "dist" + ], + "repository": { + "type": "git", + "url": "git+https://github.com/lensapp/lens.git" + }, + "main": "dist/index.js", + "types": "dist/index.d.ts", + "author": { + "name": "OpenLens Authors", + "email": "info@k8slens.dev" + }, + "license": "MIT", + "homepage": "https://github.com/lensapp/lens", + "scripts": { + "build": "webpack", + "clean": "rimraf dist/", + "dev": "webpack --mode=development --watch", + "test": "jest --coverage --runInBand", + "lint": "lens-lint", + "lint:fix": "lens-lint --fix" + }, + "peerDependencies": { + "@k8slens/list-layout": "^1.0.0-alpha.1", + "@k8slens/utilities": "^1.0.0-alpha.2", + "@ogre-tools/injectable": "^15.1.2", + "auto-bind": "^4.0.0", + "moment": "^2.29.4", + "rfc6902": "^5.0.1", + "type-fest": "^2.19.0", + "typed-regex": "^0.0.8" + }, + "devDependencies": { + "@k8slens/eslint-config": "^6.5.0-alpha.2", + "@k8slens/jest": "^6.5.0-alpha.4", + "@k8slens/typescript": "^6.5.0-alpha.2" + } +} diff --git a/packages/kube-object/src/api-types.ts b/packages/kube-object/src/api-types.ts new file mode 100644 index 0000000000..cf79ccd0ea --- /dev/null +++ b/packages/kube-object/src/api-types.ts @@ -0,0 +1,401 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import type { KubeObject } from "./kube-object"; + +/** + * The metadata for LIST requests to the KubeApi + */ +export interface KubeJsonApiListMetadata { + resourceVersion: string; + selfLink?: string; +} + +export interface KubeJsonApiDataList { + kind: string; + apiVersion: string; + items: T[]; + metadata: KubeJsonApiListMetadata; +} + +export interface KubeJsonApiData< + Metadata extends KubeJsonApiObjectMetadata = KubeJsonApiObjectMetadata, + Status = unknown, + Spec = unknown, +> { + kind: string; + apiVersion: string; + metadata: Metadata; + status?: Status; + spec?: Spec; + [otherKeys: string]: unknown; +} + +export type KubeJsonApiDataFor = K extends KubeObject + ? KubeJsonApiData + : never; + +export interface KubeObjectConstructorData { + readonly kind?: string; + readonly namespaced?: boolean; + readonly apiBase?: string; +} + +export type KubeObjectConstructor = (new (data: Data) => K) & KubeObjectConstructorData; + +export interface OwnerReference { + apiVersion: string; + kind: string; + name: string; + uid: string; + controller?: boolean; + blockOwnerDeletion?: boolean; +} + +export type KubeTemplateObjectMetadata = Pick< + KubeJsonApiObjectMetadata, + "annotations" | "finalizers" | "generateName" | "labels" | "ownerReferences" +> & { + name?: string; + namespace?: ScopedNamespace; +}; + +export interface BaseKubeJsonApiObjectMetadata { + /** + * Annotations is an unstructured key value map stored with a resource that may be set by + * external tools to store and retrieve arbitrary metadata. They are not queryable and should be + * preserved when modifying objects. + * + * More info: http://kubernetes.io/docs/user-guide/annotations + */ + annotations?: Partial>; + + /** + * The name of the cluster which the object belongs to. This is used to distinguish resources + * with same name and namespace in different clusters. This field is not set anywhere right now + * and api server is going to ignore it if set in create or update request. + */ + clusterName?: string; + + /** + * CreationTimestamp is a timestamp representing the server time when this object was created. It + * is not guaranteed to be set in happens-before order across separate operations. Clients may + * not set this value. It is represented in RFC3339 form and is in UTC. Populated by the system. + * + * More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + */ + readonly creationTimestamp?: string; + + /** + * Number of seconds allowed for this object to gracefully terminate before it will be removed + * from the system. Only set when deletionTimestamp is also set. May only be shortened. + */ + readonly deletionGracePeriodSeconds?: number; + + /** + * DeletionTimestamp is RFC 3339 date and time at which this resource will be deleted. This field + * is set by the server when a graceful deletion is requested by the user, and is not directly + * settable by a client. The resource is expected to be deleted (no longer visible from resource + * lists, and not reachable by name) after the time in this field, once the finalizers list is + * empty. As long as the finalizers list contains items, deletion is blocked. Once the + * `deletionTimestamp` is set, this value may not be unset or be set further into the future, + * although it may be shortened or the resource may be deleted prior to this time. For example, + * a user may request that a pod is deleted in 30 seconds. The Kubelet will react by sending a + * graceful termination signal to the containers in the pod. After that 30 seconds, the Kubelet + * will send a hard termination signal (SIGKILL) to the container and after cleanup, remove the + * pod from the API. In the presence of network partitions, this object may still exist after + * this timestamp, until an administrator or automated process can determine the resource is + * fully terminated. If not set, graceful deletion of the object has not been requested. + * Populated by the system when a graceful deletion is requested. + * + * More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + */ + readonly deletionTimestamp?: string; + + /** + * Must be empty before the object is deleted from the registry. Each entry is an identifier for + * the responsible component that will remove the entry from the list. If the deletionTimestamp + * of the object is non-nil, entries in this list can only be removed. Finalizers may be + * processed and removed in any order. Order is NOT enforced because it introduces significant + * risk of stuck finalizers. finalizers is a shared field, any actor with permission can reorder + * it. If the finalizer list is processed in order, then this can lead to a situation in which + * the component responsible for the first finalizer in the list is waiting for a signal (field + * value, external system, or other) produced by a component responsible for a finalizer later in + * the list, resulting in a deadlock. Without enforced ordering finalizers are free to order + * amongst themselves and are not vulnerable to ordering changes in the list. + */ + finalizers?: string[]; + + /** + * GenerateName is an optional prefix, used by the server, to generate a unique name ONLY IF the + * Name field has not been provided. If this field is used, the name returned to the client will + * be different than the name passed. This value will also be combined with a unique suffix. The + * provided value has the same validation rules as the Name field, and may be truncated by the + * length of the suffix required to make the value unique on the server. If this field is + * specified and the generated name exists, the server will NOT return a 409 - instead, it will + * either return 201 Created or 500 with Reason ServerTimeout indicating a unique name could not + * be found in the time allotted, and the client should retry (optionally after the time indicated + * in the Retry-After header). Applied only if Name is not specified. + * + * More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#idempotency + */ + generateName?: string; + + /** + * A sequence number representing a specific generation of the desired state. Populated by the + * system. + */ + readonly generation?: number; + + /** + * Map of string keys and values that can be used to organize and categorize (scope and select) + * objects. May match selectors of replication controllers and services. + * + * More info: http://kubernetes.io/docs/user-guide/labels + */ + labels?: Partial>; + + /** + * ManagedFields maps workflow-id and version to the set of fields that are managed by that + * workflow. This is mostly for internal housekeeping, and users typically shouldn't need to set + * or understand this field. A workflow can be the user's name, a controller's name, or the name + * of a specific apply path like "ci-cd". The set of fields is always in the version that the + * workflow used when modifying the object. + */ + managedFields?: unknown[]; + + /** + * Name must be unique within a namespace. Is required when creating resources, although some + * resources may allow a client to request the generation of an appropriate name automatically. + * Name is primarily intended for creation idempotence and configuration definition. + * + * More info: http://kubernetes.io/docs/user-guide/identifiers#names + */ + readonly name: string; + + /** + * Namespace defines the space within which each name must be unique. An empty namespace is + * equivalent to the "default" namespace, but "default" is the canonical representation. Not all + * objects are required to be scoped to a namespace - the value of this field for those objects + * will be empty. Must be a DNS_LABEL. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/namespaces + */ + readonly namespace?: ScopedNamespace; + + /** + * List of objects depended by this object. If ALL objects in the list have been deleted, this + * object will be garbage collected. If this object is managed by a controller, then an entry in + * this list will point to this controller, with the controller field set to true. There cannot + * be more than one managing controller. + */ + ownerReferences?: OwnerReference[]; + + /** + * An opaque value that represents the internal version of this object that can be used by + * clients to determine when objects have changed. May be used for optimistic concurrency, change + * detection, and the watch operation on a resource or set of resources. Clients must treat these + * values as opaque and passed unmodified back to the server. They may only be valid for a + * particular resource or set of resources. Populated by the system. Value must be treated as + * opaque by clients. + * + * More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency + */ + readonly resourceVersion?: string; + + /** + * SelfLink is a URL representing this object. Populated by the system. + */ + readonly selfLink?: string; + + /** + * UID is the unique in time and space value for this object. It is typically generated by the + * server on successful creation of a resource and is not allowed to change on PUT operations. + * Populated by the system. + * + * More info: http://kubernetes.io/docs/user-guide/identifiers#uids + */ + readonly uid?: string; + + [key: string]: unknown; +} + +export type KubeJsonApiObjectMetadata = + BaseKubeJsonApiObjectMetadata & + (Namespaced extends KubeObjectScope.Namespace ? { readonly namespace: string } : {}); + +export type KubeObjectMetadata = + KubeJsonApiObjectMetadata & { + readonly selfLink: string; + readonly uid: string; + readonly name: string; + readonly resourceVersion: string; + }; + +export type NamespaceScopedMetadata = KubeObjectMetadata; +export type ClusterScopedMetadata = KubeObjectMetadata; + +export interface KubeStatusData { + kind: string; + apiVersion: string; + code: number; + message?: string; + reason?: string; + status?: string; +} + +export interface EvictionObject { + kind: "Eviction"; + apiVersion: string | "policy/v1"; + metadata: Partial; + deleteOptions?: { + kind?: string; + apiVersion?: string; + dryRun?: string[]; + gracePeriodSeconds?: number; + orphanDependents?: boolean; + propagationPolicy?: string; + preconditions?: { + resourceVersion: string; + uid: string; + }[]; + }; +} + +export interface BaseKubeObjectCondition { + /** + * Last time the condition transit from one status to another. + * + * @type Date + */ + lastTransitionTime?: string; + /** + * A human readable message indicating details about last transition. + */ + message?: string; + /** + * brief (usually one word) reason for the condition's last transition. + */ + reason?: string; + /** + * Status of the condition + */ + status: "True" | "False" | "Unknown"; + /** + * Type of the condition + */ + type: string; +} + +export interface KubeObjectStatus { + conditions?: BaseKubeObjectCondition[]; +} + +export type KubeMetaField = keyof KubeJsonApiObjectMetadata; + +export class KubeCreationError extends Error { + constructor(message: string, public data: unknown) { + super(message); + } +} + +export type LabelMatchExpression = { + /** + * The label key that the selector applies to. + */ + key: string; +} & ( + | { + /** + * This represents the key's relationship to a set of values. + */ + operator: "Exists" | "DoesNotExist"; + values?: undefined; + } + | { + operator: "In" | "NotIn"; + /** + * The set of values for to match according to the operator for the label. + */ + values: string[]; + } +); + +export interface Toleration { + key?: string; + operator?: string; + effect?: string; + value?: string; + tolerationSeconds?: number; +} + +export interface ObjectReference { + apiVersion?: string; + fieldPath?: string; + kind?: string; + name: string; + namespace?: string; + resourceVersion?: string; + uid?: string; +} + +export interface LocalObjectReference { + name: string; +} + +export interface TypedLocalObjectReference { + apiGroup?: string; + kind: string; + name: string; +} + +export interface NodeAffinity { + nodeSelectorTerms?: LabelSelector[]; + weight: number; + preference: LabelSelector; +} + +export interface PodAffinity { + labelSelector: LabelSelector; + topologyKey: string; +} + +export interface SpecificAffinity { + requiredDuringSchedulingIgnoredDuringExecution?: T[]; + preferredDuringSchedulingIgnoredDuringExecution?: T[]; +} + +export interface Affinity { + nodeAffinity?: SpecificAffinity; + podAffinity?: SpecificAffinity; + podAntiAffinity?: SpecificAffinity; +} + +export interface LabelSelector { + matchLabels?: Partial>; + matchExpressions?: LabelMatchExpression[]; +} + +export enum KubeObjectScope { + Namespace, + Cluster, +} + +export type ScopedNamespace = Namespaced extends KubeObjectScope.Namespace + ? string + : Namespaced extends KubeObjectScope.Cluster + ? undefined + : string | undefined; + +export interface RawKubeObject< + Metadata extends KubeObjectMetadata = KubeObjectMetadata, + Status = Record, + Spec = Record, +> { + apiVersion: string; + kind: string; + metadata: Metadata; + status?: Status; + spec?: Spec; + [otherFields: string]: unknown; +} diff --git a/packages/kube-object/src/kube-object.ts b/packages/kube-object/src/kube-object.ts new file mode 100644 index 0000000000..af1697e636 --- /dev/null +++ b/packages/kube-object/src/kube-object.ts @@ -0,0 +1,266 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import moment from "moment"; +import { formatDuration, isObject, isString } from "@k8slens/utilities"; +import type { ItemObject } from "@k8slens/list-layout"; +import type { Patch } from "rfc6902"; +import autoBind from "auto-bind"; +import type { KubeObjectMetadata, KubeObjectScope, KubeJsonApiData } from "./api-types"; +import { KubeCreationError } from "./api-types"; +import { + isKubeObjectNonSystem, + isJsonApiData, + isKubeJsonApiListMetadata, + isKubeJsonApiMetadata, + isPartialJsonApiMetadata, + isPartialJsonApiData, + isJsonApiDataList, + stringifyLabels, + filterOutResourceApplierAnnotations, +} from "./utils"; + +export function createKubeObject< + Metadata extends KubeObjectMetadata = KubeObjectMetadata, + Status = unknown, + Spec = unknown, +>(data: KubeJsonApiData) { + return new KubeObject(data); +} + +export class KubeObject< + Metadata extends KubeObjectMetadata = KubeObjectMetadata, + Status = unknown, + Spec = unknown, +> implements ItemObject +{ + static readonly kind?: string; + + static readonly namespaced?: boolean; + + static readonly apiBase?: string; + + apiVersion!: string; + + kind!: string; + + metadata!: Metadata; + + status?: Status; + + spec!: Spec; + + /** + * @deprecated Switch to using {@link createKubeObject} instead + */ + static create = createKubeObject; + + /** + * @deprecated Switch to using {@link isKubeObjectNonSystem} instead + */ + static isNonSystem = isKubeObjectNonSystem; + + /** + * @deprecated Switch to using {@link isJsonApiData} instead + */ + static isJsonApiData = isJsonApiData; + + /** + * @deprecated Switch to using {@link isKubeJsonApiListMetadata} instead + */ + static isKubeJsonApiListMetadata = isKubeJsonApiListMetadata; + + /** + * @deprecated Switch to using {@link isKubeJsonApiMetadata} instead + */ + static isKubeJsonApiMetadata = isKubeJsonApiMetadata; + + /** + * @deprecated Switch to using {@link isPartialJsonApiMetadata} instead + */ + static isPartialJsonApiMetadata = isPartialJsonApiMetadata; + + /** + * @deprecated Switch to using {@link isPartialJsonApiData} instead + */ + static isPartialJsonApiData = isPartialJsonApiData; + + /** + * @deprecated Switch to using {@link isJsonApiDataList} instead + */ + static isJsonApiDataList = isJsonApiDataList; + + /** + * @deprecated Switch to using {@link stringifyLabels} instead + */ + static stringifyLabels = stringifyLabels; + + constructor(data: KubeJsonApiData) { + if (!isObject(data)) { + throw new TypeError(`Cannot create a KubeObject from ${typeof data}`); + } + + if (!isObject(data.metadata)) { + throw new KubeCreationError(`Cannot create a KubeObject from an object without metadata`, data); + } + + if (!isString(data.metadata.name)) { + throw new KubeCreationError( + `Cannot create a KubeObject from an object without metadata.name being a string`, + data, + ); + } + + if (!isString(data.metadata.uid)) { + throw new KubeCreationError( + `Cannot create a KubeObject from an object without metadata.uid being a string`, + data, + ); + } + + if (!isString(data.metadata.resourceVersion)) { + throw new KubeCreationError( + `Cannot create a KubeObject from an object without metadata.resourceVersion being a string`, + data, + ); + } + + if (!isString(data.metadata.selfLink)) { + throw new KubeCreationError( + `Cannot create a KubeObject from an object without metadata.selfLink being a string`, + data, + ); + } + + Object.assign(this, data); + autoBind(this); + } + + get selfLink() { + return this.metadata.selfLink; + } + + getId() { + return this.metadata.uid; + } + + getResourceVersion() { + return this.metadata.resourceVersion; + } + + getScopedName() { + return [this.getNs(), this.getName()].filter(Boolean).join("/"); + } + + getName() { + return this.metadata.name; + } + + getNs(): Metadata["namespace"] { + // avoid "null" serialization via JSON.stringify when post data + return this.metadata.namespace || undefined; + } + + /** + * This function computes the number of milliseconds from the UNIX EPOCH to the + * creation timestamp of this object. + */ + getCreationTimestamp() { + if (!this.metadata.creationTimestamp) { + return Date.now(); + } + + return new Date(this.metadata.creationTimestamp).getTime(); + } + + /** + * @deprecated This function computes a new "now". Switch to using {@link KubeObject.getCreationTimestamp} instead + */ + getTimeDiffFromNow(): number { + if (!this.metadata.creationTimestamp) { + return 0; + } + + return Date.now() - new Date(this.metadata.creationTimestamp).getTime(); + } + + /** + * @deprecated This function computes a new "now" on every call might cause subtle issues if called multiple times + * + * NOTE: this function also is not reactive to updates in the current time so it should not be used for rendering + */ + getAge(humanize = true, compact = true, fromNow = false): string | number { + if (fromNow) { + return moment(this.metadata.creationTimestamp).fromNow(); // "string", getTimeDiffFromNow() cannot be used + } + const diff = this.getTimeDiffFromNow(); + + if (humanize) { + return formatDuration(diff, compact); + } + + return diff; + } + + getFinalizers(): string[] { + return this.metadata.finalizers || []; + } + + getLabels(): string[] { + return KubeObject.stringifyLabels(this.metadata.labels); + } + + getAnnotations(filter = false): string[] { + const labels = KubeObject.stringifyLabels(this.metadata.annotations); + + if (!filter) { + return labels; + } + + return labels.filter(filterOutResourceApplierAnnotations); + } + + getOwnerRefs() { + const refs = this.metadata.ownerReferences || []; + const namespace = this.getNs(); + + return refs.map((ownerRef) => ({ ...ownerRef, namespace })); + } + + getSearchFields() { + return [this.getName(), this.getNs(), this.getId(), ...this.getLabels(), ...this.getAnnotations(true)]; + } + + toPlainObject() { + return JSON.parse(JSON.stringify(this)) as Record; + } + + /** + * @deprecated use KubeApi.patch instead + */ + async patch(patch: Patch): Promise { + void patch; + + throw new Error("KubeObject.patch() has been deprecated since v5.3.0, please switch to using KubeApi.patch()"); + } + + /** + * @deprecated use KubeApi.patch instead + */ + async update(data: Partial): Promise { + void data; + + throw new Error("KubeObject.update() has been deprecated since v5.3.0, please switch to using KubeApi.patch()"); + } + + /** + * @deprecated use KubeApi.delete instead + */ + delete(params?: object) { + void params; + + throw new Error("KubeObject.delete() has been deprecated since v5.3.0, please switch to using KubeApi.delete()"); + } +} diff --git a/packages/kube-object/src/kube-status.ts b/packages/kube-object/src/kube-status.ts new file mode 100644 index 0000000000..bbf7a3e333 --- /dev/null +++ b/packages/kube-object/src/kube-status.ts @@ -0,0 +1,53 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import { isObject, hasTypedProperty, hasOptionalTypedProperty, isString, isNumber } from "@k8slens/utilities"; +import type { KubeStatusData } from "./api-types"; + +/** + * Is the shape of {@link object} correct for {@link KubeStatusData} + * @param object Some object + * @returns + */ +export function isKubeStatusData(object: unknown): object is KubeStatusData { + return ( + isObject(object) && + hasTypedProperty(object, "kind", isString) && + hasTypedProperty(object, "apiVersion", isString) && + hasTypedProperty(object, "code", isNumber) && + (hasOptionalTypedProperty(object, "message", isString) || + hasOptionalTypedProperty(object, "reason", isString) || + hasOptionalTypedProperty(object, "status", isString)) && + object.kind === "Status" + ); +} + +export class KubeStatus { + public readonly kind = "Status"; + + public readonly apiVersion: string; + + public readonly code: number; + + public readonly message: string; + + public readonly reason: string; + + public readonly status: string; + + constructor(data: KubeStatusData) { + this.apiVersion = data.apiVersion; + this.code = data.code; + this.message = data.message || ""; + this.reason = data.reason || ""; + this.status = data.status || ""; + } + + getExplanation(): string { + const { code, message, reason, status } = this; + + return `${code}: ${message || reason || status}`; + } +} diff --git a/packages/kube-object/src/specifics/cluster-role-binding.ts b/packages/kube-object/src/specifics/cluster-role-binding.ts new file mode 100644 index 0000000000..06b4010b59 --- /dev/null +++ b/packages/kube-object/src/specifics/cluster-role-binding.ts @@ -0,0 +1,43 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import type { KubeJsonApiData, KubeObjectMetadata, KubeObjectScope, ClusterScopedMetadata } from "../api-types"; +import { KubeObject } from "../kube-object"; +import type { RoleRef } from "../types/role-ref"; +import type { Subject } from "../types/subject"; + +export interface ClusterRoleBindingData + extends KubeJsonApiData, void, void> { + subjects?: Subject[]; + roleRef: RoleRef; +} + +export class ClusterRoleBinding extends KubeObject { + static kind = "ClusterRoleBinding"; + + static namespaced = false; + + static apiBase = "/apis/rbac.authorization.k8s.io/v1/clusterrolebindings"; + + subjects?: Subject[]; + + roleRef: RoleRef; + + constructor({ subjects, roleRef, ...rest }: ClusterRoleBindingData) { + super(rest); + this.subjects = subjects; + this.roleRef = roleRef; + } + + getSubjects() { + return this.subjects ?? []; + } + + getSubjectNames(): string { + return this.getSubjects() + .map((subject) => subject.name) + .join(", "); + } +} diff --git a/packages/kube-object/src/specifics/cluster-role.ts b/packages/kube-object/src/specifics/cluster-role.ts new file mode 100644 index 0000000000..a14b47fcb7 --- /dev/null +++ b/packages/kube-object/src/specifics/cluster-role.ts @@ -0,0 +1,36 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import type { KubeJsonApiData, KubeObjectMetadata, KubeObjectScope, ClusterScopedMetadata } from "../api-types"; +import { KubeObject } from "../kube-object"; +import type { AggregationRule } from "../types/aggregation-rule"; +import type { PolicyRule } from "../types/policy-rule"; + +export interface ClusterRoleData extends KubeJsonApiData, void, void> { + rules?: PolicyRule[]; + aggregationRule?: AggregationRule; +} + +export class ClusterRole extends KubeObject { + static kind = "ClusterRole"; + + static namespaced = false; + + static apiBase = "/apis/rbac.authorization.k8s.io/v1/clusterroles"; + + rules?: PolicyRule[]; + + aggregationRule?: AggregationRule; + + constructor({ rules, aggregationRule, ...rest }: ClusterRoleData) { + super(rest); + this.rules = rules; + this.aggregationRule = aggregationRule; + } + + getRules() { + return this.rules || []; + } +} diff --git a/packages/kube-object/src/specifics/cluster.ts b/packages/kube-object/src/specifics/cluster.ts new file mode 100644 index 0000000000..33c7b957cf --- /dev/null +++ b/packages/kube-object/src/specifics/cluster.ts @@ -0,0 +1,71 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import { KubeObject } from "../kube-object"; + +export enum ClusterStatus { + ACTIVE = "Active", + CREATING = "Creating", + REMOVING = "Removing", + ERROR = "Error", +} + +export interface Cluster { + spec: { + clusterNetwork?: { + serviceDomain?: string; + pods?: { + cidrBlocks?: string[]; + }; + services?: { + cidrBlocks?: string[]; + }; + }; + providerSpec: { + value: { + profile: string; + }; + }; + }; + status?: { + apiEndpoints: { + host: string; + port: string; + }[]; + providerStatus: { + adminUser?: string; + adminPassword?: string; + kubeconfig?: string; + processState?: string; + lensAddress?: string; + }; + errorMessage?: string; + errorReason?: string; + }; +} + +export class Cluster extends KubeObject { + static kind = "Cluster"; + + static apiBase = "/apis/cluster.k8s.io/v1alpha1/clusters"; + + static namespaced = true; + + getStatus() { + if (this.metadata.deletionTimestamp) { + return ClusterStatus.REMOVING; + } + + if (!this.status || !this.status) { + return ClusterStatus.CREATING; + } + + if (this.status.errorMessage) { + return ClusterStatus.ERROR; + } + + return ClusterStatus.ACTIVE; + } +} diff --git a/packages/kube-object/src/specifics/component-status.ts b/packages/kube-object/src/specifics/component-status.ts new file mode 100644 index 0000000000..bda353cb1b --- /dev/null +++ b/packages/kube-object/src/specifics/component-status.ts @@ -0,0 +1,28 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import { KubeObject } from "../kube-object"; + +export interface ComponentStatusCondition { + type: string; + status: string; + message: string; +} + +export interface ComponentStatus { + conditions: ComponentStatusCondition[]; +} + +export class ComponentStatus extends KubeObject { + static kind = "ComponentStatus"; + + static namespaced = false; + + static apiBase = "/api/v1/componentstatuses"; + + getTruthyConditions() { + return this.conditions.filter((c) => c.status === "True"); + } +} diff --git a/packages/kube-object/src/specifics/config-map.ts b/packages/kube-object/src/specifics/config-map.ts new file mode 100644 index 0000000000..24f8bef00b --- /dev/null +++ b/packages/kube-object/src/specifics/config-map.ts @@ -0,0 +1,41 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import autoBind from "auto-bind"; +import type { KubeJsonApiData, KubeObjectMetadata, KubeObjectScope, NamespaceScopedMetadata } from "../api-types"; +import { KubeObject } from "../kube-object"; + +export interface ConfigMapData extends KubeJsonApiData, void, void> { + data?: Partial>; + binaryData?: Partial>; + immutable?: boolean; +} + +export class ConfigMap extends KubeObject { + static kind = "ConfigMap"; + + static namespaced = true; + + static apiBase = "/api/v1/configmaps"; + + data: Partial>; + + binaryData: Partial>; + + immutable?: boolean; + + constructor({ data, binaryData, immutable, ...rest }: ConfigMapData) { + super(rest); + autoBind(this); + + this.data = data ?? {}; + this.binaryData = binaryData ?? {}; + this.immutable = immutable; + } + + getKeys(): string[] { + return Object.keys(this.data); + } +} diff --git a/packages/kube-object/src/specifics/cron-job.ts b/packages/kube-object/src/specifics/cron-job.ts new file mode 100644 index 0000000000..84ebe53d9b --- /dev/null +++ b/packages/kube-object/src/specifics/cron-job.ts @@ -0,0 +1,69 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import { formatDuration } from "@k8slens/utilities"; +import moment from "moment"; +import type { ObjectReference, NamespaceScopedMetadata } from "../api-types"; +import { KubeObject } from "../kube-object"; +import type { JobTemplateSpec } from "../types/job-template-spec"; + +export interface CronJobSpec { + concurrencyPolicy?: string; + failedJobsHistoryLimit?: number; + jobTemplate?: JobTemplateSpec; + schedule: string; + startingDeadlineSeconds?: number; + successfulJobsHistoryLimit?: number; + suspend?: boolean; +} + +export interface CronJobStatus { + lastScheduleTime?: string; + lastSuccessfulTime?: string; + active?: ObjectReference[]; +} + +export class CronJob extends KubeObject { + static readonly kind = "CronJob"; + + static readonly namespaced = true; + + static readonly apiBase = "/apis/batch/v1/cronjobs"; + + getSuspendFlag() { + return (this.spec.suspend ?? false).toString(); + } + + getLastScheduleTime() { + if (!this.status?.lastScheduleTime) { + return "-"; + } + const diff = moment().diff(this.status.lastScheduleTime); + + return formatDuration(diff, true); + } + + getSchedule() { + return this.spec.schedule; + } + + isNeverRun() { + const schedule = this.getSchedule(); + const daysInMonth = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; + const stamps = schedule.split(" "); + const day = Number(stamps[stamps.length - 3]); // 1-31 + const month = Number(stamps[stamps.length - 2]); // 1-12 + + if (schedule.startsWith("@")) { + return false; + } + + return day > daysInMonth[month - 1]; + } + + isSuspend() { + return this.spec.suspend; + } +} diff --git a/packages/kube-object/src/specifics/custom-resource-definition.ts b/packages/kube-object/src/specifics/custom-resource-definition.ts new file mode 100644 index 0000000000..b450b0066c --- /dev/null +++ b/packages/kube-object/src/specifics/custom-resource-definition.ts @@ -0,0 +1,220 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import { buildURL } from "@k8slens/utilities"; +import type { BaseKubeObjectCondition, ClusterScopedMetadata } from "../api-types"; +import { KubeObject } from "../kube-object"; +import type { JSONSchemaProps } from "../types/json-schema-props"; +import type { WebhookClientConfig } from "./mutating-webhook-configuration"; + +export interface AdditionalPrinterColumnsCommon { + name: string; + type: "integer" | "number" | "string" | "boolean" | "date"; + priority?: number; + format?: "int32" | "int64" | "float" | "double" | "byte" | "binary" | "date" | "date-time" | "password"; + description?: string; +} + +export type AdditionalPrinterColumnsV1 = AdditionalPrinterColumnsCommon & { + jsonPath: string; +}; + +type AdditionalPrinterColumnsV1Beta = AdditionalPrinterColumnsCommon & { + JSONPath: string; +}; + +export interface CustomResourceValidation { + openAPIV3Schema?: JSONSchemaProps; +} + +export interface CustomResourceDefinitionVersion { + name: string; + served: boolean; + storage: boolean; + schema?: CustomResourceValidation; // required in v1 but not present in v1beta + additionalPrinterColumns?: AdditionalPrinterColumnsV1[]; +} + +export interface CustomResourceDefinitionNames { + categories?: string[]; + kind: string; + listKind?: string; + plural: string; + shortNames?: string[]; + singular?: string; +} + +export interface CustomResourceConversion { + strategy?: string; + webhook?: WebhookConversion; +} + +export interface WebhookConversion { + clientConfig?: WebhookClientConfig[]; + conversionReviewVersions: string[]; +} + +export interface CustomResourceDefinitionSpec { + group: string; + /** + * @deprecated for apiextensions.k8s.io/v1 but used in v1beta1 + */ + version?: string; + names: CustomResourceDefinitionNames; + scope: "Namespaced" | "Cluster"; + /** + * @deprecated for apiextensions.k8s.io/v1 but used in v1beta1 + */ + validation?: object; + versions?: CustomResourceDefinitionVersion[]; + conversion?: CustomResourceConversion; + /** + * @deprecated for apiextensions.k8s.io/v1 but used in v1beta1 + */ + additionalPrinterColumns?: AdditionalPrinterColumnsV1Beta[]; + preserveUnknownFields?: boolean; +} + +export interface CustomResourceDefinitionConditionAcceptedNames { + plural: string; + singular: string; + kind: string; + shortNames: string[]; + listKind: string; +} + +export interface CustomResourceDefinitionStatus { + conditions?: BaseKubeObjectCondition[]; + acceptedNames: CustomResourceDefinitionConditionAcceptedNames; + storedVersions: string[]; +} + +export class CustomResourceDefinition extends KubeObject< + ClusterScopedMetadata, + CustomResourceDefinitionStatus, + CustomResourceDefinitionSpec +> { + static kind = "CustomResourceDefinition"; + + static namespaced = false; + + static apiBase = "/apis/apiextensions.k8s.io/v1/customresourcedefinitions"; + + getResourceUrl() { + // TODO: replace this magic string with a use of `customResourcesRouteInjectable` when that is extracted + return buildURL("/crd/:group?/:name?", { + params: { + group: this.getGroup(), + name: this.getPluralName(), + }, + }); + } + + getResourceApiBase() { + const { group } = this.spec; + + return `/apis/${group}/${this.getVersion()}/${this.getPluralName()}`; + } + + getPluralName() { + return this.getNames().plural; + } + + getResourceKind() { + return this.spec.names.kind; + } + + getResourceTitle() { + const name = this.getPluralName(); + + return name[0].toUpperCase() + name.slice(1); + } + + getGroup() { + return this.spec.group; + } + + getScope() { + return this.spec.scope; + } + + getPreferedVersion(): CustomResourceDefinitionVersion { + const { apiVersion } = this; + + switch (apiVersion) { + case "apiextensions.k8s.io/v1": + for (const version of this.spec.versions ?? []) { + if (version.storage) { + return version; + } + } + break; + + case "apiextensions.k8s.io/v1beta1": { + const { additionalPrinterColumns: apc } = this.spec; + const additionalPrinterColumns = apc?.map(({ JSONPath, ...apc }) => ({ ...apc, jsonPath: JSONPath })); + + return { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + name: this.spec.version!, + served: true, + storage: true, + schema: this.spec.validation, + additionalPrinterColumns, + }; + } + } + + throw new Error( + `Unknown apiVersion=${apiVersion}: Failed to find a version for CustomResourceDefinition ${this.metadata.name}`, + ); + } + + getVersion() { + return this.getPreferedVersion().name; + } + + isNamespaced() { + return this.getScope() === "Namespaced"; + } + + getStoredVersions() { + return this.status?.storedVersions.join(", ") ?? ""; + } + + getNames() { + return this.spec.names; + } + + getConversion() { + return JSON.stringify(this.spec.conversion); + } + + getPrinterColumns(ignorePriority = true): AdditionalPrinterColumnsV1[] { + const columns = this.getPreferedVersion().additionalPrinterColumns ?? []; + + return columns.filter((column) => column.name.toLowerCase() !== "age" && (ignorePriority || !column.priority)); + } + + getValidation() { + return JSON.stringify(this.getPreferedVersion().schema, null, 2); + } + + getConditions() { + if (!this.status?.conditions) { + return []; + } + + return this.status.conditions.map((condition) => { + const { message, reason, lastTransitionTime, status } = condition; + + return { + ...condition, + isReady: status === "True", + tooltip: `${message || reason} (${lastTransitionTime})`, + }; + }); + } +} diff --git a/packages/kube-object/src/specifics/daemon-set.ts b/packages/kube-object/src/specifics/daemon-set.ts new file mode 100644 index 0000000000..db29d2719a --- /dev/null +++ b/packages/kube-object/src/specifics/daemon-set.ts @@ -0,0 +1,77 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import type { LabelSelector, KubeObjectStatus, NamespaceScopedMetadata } from "../api-types"; +import { KubeObject } from "../kube-object"; +import type { PodTemplateSpec } from "../types/pod-template-spec"; + +export interface RollingUpdateDaemonSet { + maxUnavailable?: number | string; + maxSurge?: number | string; +} + +export interface DaemonSetUpdateStrategy { + type: string; + rollingUpdate: RollingUpdateDaemonSet; +} + +export interface DaemonSetSpec { + selector: LabelSelector; + template: PodTemplateSpec; + updateStrategy: DaemonSetUpdateStrategy; + minReadySeconds?: number; + revisionHistoryLimit?: number; +} + +export interface DaemonSetStatus extends KubeObjectStatus { + collisionCount?: number; + currentNumberScheduled: number; + desiredNumberScheduled: number; + numberAvailable?: number; + numberMisscheduled: number; + numberReady: number; + numberUnavailable?: number; + observedGeneration?: number; + updatedNumberScheduled?: number; +} + +export class DaemonSet extends KubeObject { + static kind = "DaemonSet"; + + static namespaced = true; + + static apiBase = "/apis/apps/v1/daemonsets"; + + getSelectors(): string[] { + return KubeObject.stringifyLabels(this.spec.selector.matchLabels); + } + + getNodeSelectors(): string[] { + return KubeObject.stringifyLabels(this.spec.template.spec?.nodeSelector); + } + + getTemplateLabels(): string[] { + return KubeObject.stringifyLabels(this.spec.template.metadata?.labels); + } + + getTolerations() { + return this.spec.template.spec?.tolerations ?? []; + } + + getAffinity() { + return this.spec.template.spec?.affinity; + } + + getAffinityNumber() { + return Object.keys(this.getAffinity() ?? {}).length; + } + + getImages() { + const containers = this.spec.template?.spec?.containers ?? []; + const initContainers = this.spec.template?.spec?.initContainers ?? []; + + return [...containers, ...initContainers].map((container) => container.image); + } +} diff --git a/packages/kube-object/src/specifics/deployment.ts b/packages/kube-object/src/specifics/deployment.ts new file mode 100644 index 0000000000..28311b8c53 --- /dev/null +++ b/packages/kube-object/src/specifics/deployment.ts @@ -0,0 +1,89 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import type { LabelSelector, KubeObjectStatus, NamespaceScopedMetadata } from "../api-types"; +import { KubeObject } from "../kube-object"; +import type { PodSpec } from "./pod"; + +export interface DeploymentSpec { + replicas: number; + selector: LabelSelector; + template: { + metadata: { + creationTimestamp?: string; + labels: Partial>; + annotations?: Partial>; + }; + spec: PodSpec; + }; + strategy: { + type: string; + rollingUpdate: { + maxUnavailable: number; + maxSurge: number; + }; + }; +} + +export interface DeploymentStatus extends KubeObjectStatus { + observedGeneration: number; + replicas: number; + updatedReplicas: number; + readyReplicas: number; + availableReplicas?: number; + unavailableReplicas?: number; +} + +export class Deployment extends KubeObject { + static kind = "Deployment"; + + static namespaced = true; + + static apiBase = "/apis/apps/v1/deployments"; + + getSelectors(): string[] { + return KubeObject.stringifyLabels(this.spec.selector.matchLabels); + } + + getNodeSelectors(): string[] { + return KubeObject.stringifyLabels(this.spec.template.spec.nodeSelector); + } + + getTemplateLabels(): string[] { + return KubeObject.stringifyLabels(this.spec.template.metadata.labels); + } + + getTolerations() { + return this.spec.template.spec.tolerations ?? []; + } + + getAffinity() { + return this.spec.template.spec.affinity; + } + + getAffinityNumber() { + return Object.keys(this.getAffinity() ?? {}).length; + } + + getConditions(activeOnly = false) { + const { conditions = [] } = this.status ?? {}; + + if (activeOnly) { + return conditions.filter((c) => c.status === "True"); + } + + return conditions; + } + + getConditionsText(activeOnly = true) { + return this.getConditions(activeOnly) + .map(({ type }) => type) + .join(" "); + } + + getReplicas() { + return this.spec.replicas || 0; + } +} diff --git a/packages/kube-object/src/specifics/endpoint.ts b/packages/kube-object/src/specifics/endpoint.ts new file mode 100644 index 0000000000..6dba5a2ef9 --- /dev/null +++ b/packages/kube-object/src/specifics/endpoint.ts @@ -0,0 +1,102 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import autoBind from "auto-bind"; +import type { + ObjectReference, + KubeJsonApiData, + KubeObjectMetadata, + KubeObjectScope, + NamespaceScopedMetadata, +} from "../api-types"; +import { KubeObject } from "../kube-object"; + +export function formatEndpointSubset(subset: EndpointSubset): string { + const { addresses, ports } = subset; + + if (!addresses || !ports) { + return ""; + } + + return addresses.map((address) => ports.map((port) => `${address.ip}:${port.port}`).join(", ")).join(", "); +} + +export interface ForZone { + name: string; +} + +export interface EndpointHints { + forZones?: ForZone[]; +} + +export interface EndpointConditions { + ready?: boolean; + serving?: boolean; + terminating?: boolean; +} + +export interface EndpointData { + addresses: string[]; + conditions?: EndpointConditions; + hints?: EndpointHints; + hostname?: string; + nodeName?: string; + targetRef?: ObjectReference; + zone?: string; +} + +export interface EndpointPort { + appProtocol?: string; + name?: string; + protocol?: string; + port: number; +} + +export interface EndpointAddress { + hostname?: string; + ip: string; + nodeName?: string; + targetRef?: ObjectReference; +} + +export interface EndpointSubset { + addresses?: EndpointAddress[]; + notReadyAddresses?: EndpointAddress[]; + ports?: EndpointPort[]; +} + +export interface EndpointsData extends KubeJsonApiData, void, void> { + subsets?: EndpointSubset[]; +} + +export class Endpoints extends KubeObject { + static kind = "Endpoints"; + + static namespaced = true; + + static apiBase = "/api/v1/endpoints"; + + subsets?: EndpointSubset[]; + + constructor({ subsets, ...rest }: EndpointsData) { + super(rest); + autoBind(this); + this.subsets = subsets; + } + + getEndpointSubsets(): Required[] { + return ( + this.subsets?.map(({ addresses = [], notReadyAddresses = [], ports = [] }) => ({ + addresses, + notReadyAddresses, + ports, + })) ?? [] + ); + } + + toString(): string { + return this.getEndpointSubsets().map(formatEndpointSubset).join(", ") || ""; + } +} diff --git a/packages/kube-object/src/specifics/events.ts b/packages/kube-object/src/specifics/events.ts new file mode 100644 index 0000000000..ec8e33de43 --- /dev/null +++ b/packages/kube-object/src/specifics/events.ts @@ -0,0 +1,143 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import { formatDuration } from "@k8slens/utilities"; +import moment from "moment"; +import type { KubeJsonApiData, KubeObjectMetadata, KubeObjectScope, ObjectReference } from "../api-types"; +import { KubeObject } from "../kube-object"; + +export interface EventSeries { + count?: number; + lastObservedTime?: string; +} + +export interface EventSource { + component?: string; + host?: string; +} + +export interface KubeEventData extends KubeJsonApiData, void, void> { + action?: string; + count?: number; + eventTime?: string; + firstTimestamp?: string; + involvedObject: Required; + lastTimestamp?: string; + message?: string; + reason?: string; + related?: ObjectReference; + reportingComponent?: string; + reportingInstance?: string; + series?: EventSeries; + source?: EventSource; + type?: string; +} + +export class KubeEvent extends KubeObject, void, void> { + static kind = "Event"; + + static namespaced = true; + + static apiBase = "/api/v1/events"; + + action?: string; + + count?: number; + + eventTime?: string; + + firstTimestamp?: string; + + involvedObject: Required; + + lastTimestamp?: string; + + message?: string; + + reason?: string; + + related?: ObjectReference; + + reportingComponent?: string; + + reportingInstance?: string; + + series?: EventSeries; + + source?: EventSource; + + /** + * Current supported values are: + * - "Normal" + * - "Warning" + */ + type?: string; + + constructor({ + action, + count, + eventTime, + firstTimestamp, + involvedObject, + lastTimestamp, + message, + reason, + related, + reportingComponent, + reportingInstance, + series, + source, + type, + ...rest + }: KubeEventData) { + super(rest); + this.action = action; + this.count = count; + this.eventTime = eventTime; + this.firstTimestamp = firstTimestamp; + this.involvedObject = involvedObject; + this.lastTimestamp = lastTimestamp; + this.message = message; + this.reason = reason; + this.related = related; + this.reportingComponent = reportingComponent; + this.reportingInstance = reportingInstance; + this.series = series; + this.source = source; + this.type = type; + } + + isWarning() { + return this.type === "Warning"; + } + + getSource() { + if (!this.source?.component) { + return ""; + } + + const { component, host = "" } = this.source; + + return `${component} ${host}`; + } + + /** + * @deprecated This function is not reactive to changing of time. If rendering use `` instead + */ + getFirstSeenTime() { + const diff = moment().diff(this.firstTimestamp); + + return formatDuration(diff, true); + } + + /** + * @deprecated This function is not reactive to changing of time. If rendering use `` instead + */ + getLastSeenTime() { + const diff = moment().diff(this.lastTimestamp); + + return formatDuration(diff, true); + } +} diff --git a/packages/kube-object/src/specifics/horizontal-pod-autoscaler.ts b/packages/kube-object/src/specifics/horizontal-pod-autoscaler.ts new file mode 100644 index 0000000000..0707c659b5 --- /dev/null +++ b/packages/kube-object/src/specifics/horizontal-pod-autoscaler.ts @@ -0,0 +1,340 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import type { OptionVariant } from "@k8slens/utilities"; +import type { LabelSelector, BaseKubeObjectCondition, NamespaceScopedMetadata } from "../api-types"; +import { KubeObject } from "../kube-object"; +import type { CrossVersionObjectReference } from "../types/cross-version-object-reference"; + +export type HpaMetricType = "Resource" | "Pods" | "Object" | "External" | "ContainerResource"; + +export interface MetricCurrentTarget { + current?: string; + target?: string; +} + +export interface HorizontalPodAutoscalerMetricTarget { + kind: string; + name: string; + apiVersion: string; +} + +export interface V2ContainerResourceMetricSource { + container: string; + name: string; + target?: { + averageUtilization?: number; + averageValue?: string; + type?: string; + }; +} + +export interface V2Beta1ContainerResourceMetricSource { + container: string; + name: string; + targetAverageUtilization?: number; + targetAverageValue?: string; +} + +export type ContainerResourceMetricSource = V2ContainerResourceMetricSource | V2Beta1ContainerResourceMetricSource; + +export interface V2ExternalMetricSource { + metricName?: string; + metricSelector?: LabelSelector; + metric?: { + name?: string; + selector?: LabelSelector; + }; + target?: { + type: string; + value?: string; + averageValue?: string; + }; +} + +export interface V2Beta1ExternalMetricSource { + metricName?: string; + metricSelector?: LabelSelector; + targetAverageValue?: string; + targetValue?: string; + metric?: { + selector?: LabelSelector; + }; +} + +export type ExternalMetricSource = V2Beta1ExternalMetricSource | V2ExternalMetricSource; + +export interface V2ObjectMetricSource { + metric?: { + name?: string; + selector?: LabelSelector; + }; + target?: { + type?: string; + value?: string; + averageValue?: string; + }; + describedObject?: CrossVersionObjectReference; +} + +export interface V2Beta1ObjectMetricSource { + averageValue?: string; + metricName?: string; + selector?: LabelSelector; + targetValue?: string; + describedObject?: CrossVersionObjectReference; +} + +export type ObjectMetricSource = V2ObjectMetricSource | V2Beta1ObjectMetricSource; + +export interface V2PodsMetricSource { + metric?: { + name?: string; + selector?: LabelSelector; + }; + target?: { + averageValue?: string; + type?: string; + }; +} + +export interface V2Beta1PodsMetricSource { + metricName?: string; + selector?: LabelSelector; + targetAverageValue?: string; +} + +export type PodsMetricSource = V2PodsMetricSource | V2Beta1PodsMetricSource; + +export interface V2ResourceMetricSource { + name: string; + target?: { + averageUtilization?: number; + averageValue?: string; + type?: string; + }; +} + +export interface V2Beta1ResourceMetricSource { + name: string; + targetAverageUtilization?: number; + targetAverageValue?: string; +} + +export type ResourceMetricSource = V2ResourceMetricSource | V2Beta1ResourceMetricSource; + +export interface BaseHorizontalPodAutoscalerMetricSpec { + containerResource: ContainerResourceMetricSource; + external: ExternalMetricSource; + object: ObjectMetricSource; + pods: PodsMetricSource; + resource: ResourceMetricSource; +} + +export type HorizontalPodAutoscalerMetricSpec = + | OptionVariant<"Resource", BaseHorizontalPodAutoscalerMetricSpec, "resource"> + | OptionVariant<"External", BaseHorizontalPodAutoscalerMetricSpec, "external"> + | OptionVariant<"Object", BaseHorizontalPodAutoscalerMetricSpec, "object"> + | OptionVariant<"Pods", BaseHorizontalPodAutoscalerMetricSpec, "pods"> + | OptionVariant<"ContainerResource", BaseHorizontalPodAutoscalerMetricSpec, "containerResource">; + +interface HorizontalPodAutoscalerBehavior { + scaleUp?: HPAScalingRules; + scaleDown?: HPAScalingRules; +} + +interface HPAScalingRules { + stabilizationWindowSecond?: number; + selectPolicy?: ScalingPolicySelect; + policies?: HPAScalingPolicy[]; +} + +type ScalingPolicySelect = string; + +interface HPAScalingPolicy { + type: HPAScalingPolicyType; + value: number; + periodSeconds: number; +} + +type HPAScalingPolicyType = string; + +export interface V2ContainerResourceMetricStatus { + container?: string; + name: string; + current?: { + averageUtilization?: number; + averageValue?: string; + }; +} + +export interface V2Beta1ContainerResourceMetricStatus { + container?: string; + currentAverageUtilization?: number; + currentAverageValue?: string; + name: string; +} + +export type ContainerResourceMetricStatus = V2ContainerResourceMetricStatus | V2Beta1ContainerResourceMetricStatus; + +export interface V2ExternalMetricStatus { + metric?: { + name?: string; + selector?: LabelSelector; + }; + current?: { + averageValue?: string; + value?: string; + }; +} + +export interface V2Beta1ExternalMetricStatus { + currentAverageValue?: string; + currentValue?: string; + metricName?: string; + metricSelector?: LabelSelector; +} + +export type ExternalMetricStatus = V2Beta1ExternalMetricStatus | V2ExternalMetricStatus; + +export interface V2ObjectMetricStatus { + metric?: { + name?: string; + selector?: LabelSelector; + }; + current?: { + type?: string; + value?: string; + averageValue?: string; + }; + describedObject?: CrossVersionObjectReference; +} + +export interface V2Beta1ObjectMetricStatus { + averageValue?: string; + currentValue?: string; + metricName?: string; + selector?: LabelSelector; + describedObject?: CrossVersionObjectReference; +} + +export type ObjectMetricStatus = V2Beta1ObjectMetricStatus | V2ObjectMetricStatus; + +export interface V2PodsMetricStatus { + selector?: LabelSelector; + metric?: { + name?: string; + selector?: LabelSelector; + }; + current?: { + averageValue?: string; + }; +} + +export interface V2Beta1PodsMetricStatus { + currentAverageValue?: string; + metricName?: string; + selector?: LabelSelector; +} + +export type PodsMetricStatus = V2Beta1PodsMetricStatus | V2PodsMetricStatus; + +export interface V2ResourceMetricStatus { + name: string; + current?: { + averageUtilization?: number; + averageValue?: string; + }; +} + +export interface V2Beta1ResourceMetricStatus { + currentAverageUtilization?: number; + currentAverageValue?: string; + name: string; +} + +export type ResourceMetricStatus = V2Beta1ResourceMetricStatus | V2ResourceMetricStatus; + +export interface BaseHorizontalPodAutoscalerMetricStatus { + containerResource: ContainerResourceMetricStatus; + external: ExternalMetricStatus; + object: ObjectMetricStatus; + pods: PodsMetricStatus; + resource: ResourceMetricStatus; +} + +export type HorizontalPodAutoscalerMetricStatus = + | OptionVariant<"Resource", BaseHorizontalPodAutoscalerMetricStatus, "resource"> + | OptionVariant<"External", BaseHorizontalPodAutoscalerMetricStatus, "external"> + | OptionVariant<"Object", BaseHorizontalPodAutoscalerMetricStatus, "object"> + | OptionVariant<"Pods", BaseHorizontalPodAutoscalerMetricStatus, "pods"> + | OptionVariant<"ContainerResource", BaseHorizontalPodAutoscalerMetricStatus, "containerResource">; + +export interface HorizontalPodAutoscalerSpec { + scaleTargetRef: CrossVersionObjectReference; + minReplicas?: number; + maxReplicas: number; + metrics?: HorizontalPodAutoscalerMetricSpec[]; + behavior?: HorizontalPodAutoscalerBehavior; + targetCPUUtilizationPercentage?: number; // used only in autoscaling/v1 +} + +export interface HorizontalPodAutoscalerStatus { + conditions?: BaseKubeObjectCondition[]; + currentReplicas: number; + desiredReplicas: number; + currentMetrics?: HorizontalPodAutoscalerMetricStatus[]; + currentCPUUtilizationPercentage?: number; // used only in autoscaling/v1 +} + +export class HorizontalPodAutoscaler extends KubeObject< + NamespaceScopedMetadata, + HorizontalPodAutoscalerStatus, + HorizontalPodAutoscalerSpec +> { + static readonly kind = "HorizontalPodAutoscaler"; + + static readonly namespaced = true; + + static readonly apiBase = "/apis/autoscaling/v2/horizontalpodautoscalers"; + + getMaxPods() { + return this.spec.maxReplicas ?? 0; + } + + getMinPods() { + return this.spec.minReplicas ?? 0; + } + + getReplicas() { + return this.status?.currentReplicas ?? 0; + } + + getReadyConditions() { + return this.getConditions().filter(({ isReady }) => isReady); + } + + getConditions() { + return ( + this.status?.conditions?.map((condition) => { + const { message, reason, lastTransitionTime, status } = condition; + + return { + ...condition, + isReady: status === "True", + tooltip: `${message || reason} (${lastTransitionTime})`, + }; + }) ?? [] + ); + } + + getMetrics() { + return this.spec.metrics ?? []; + } + + getCurrentMetrics() { + return this.status?.currentMetrics ?? []; + } +} diff --git a/packages/kube-object/src/specifics/index.ts b/packages/kube-object/src/specifics/index.ts new file mode 100644 index 0000000000..bab5ab47c7 --- /dev/null +++ b/packages/kube-object/src/specifics/index.ts @@ -0,0 +1,47 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +export * from "./cluster-role-binding"; +export * from "./cluster-role"; +export * from "./cluster"; +export * from "./component-status"; +export * from "./config-map"; +export * from "./cron-job"; +export * from "./custom-resource-definition"; +export * from "./daemon-set"; +export * from "./deployment"; +export * from "./endpoint"; +export * from "./events"; +export * from "./horizontal-pod-autoscaler"; +export * from "./ingress-class"; +export * from "./ingress"; +export * from "./job"; +export * from "./lease"; +export * from "./limit-range"; +export * from "./mutating-webhook-configuration"; +export * from "./namespace"; +export * from "./network-policy"; +export * from "./node"; +export * from "./persistent-volume-claim"; +export * from "./persistent-volume"; +export * from "./pod-disruption-budget"; +export * from "./pod-metrics"; +export * from "./pod-security-policy"; +export * from "./pod"; +export * from "./priority-class"; +export * from "./replica-set"; +export * from "./replication-controller"; +export * from "./resource-quota"; +export * from "./role-binding"; +export * from "./role"; +export * from "./runtime-class"; +export * from "./secret"; +export * from "./self-subject-rules-reviews"; +export * from "./service-account"; +export * from "./service"; +export * from "./stateful-set"; +export * from "./storage-class"; +export * from "./validating-webhook-configuration"; +export * from "./vertical-pod-autoscaler"; diff --git a/packages/kube-object/src/specifics/ingress-class.ts b/packages/kube-object/src/specifics/ingress-class.ts new file mode 100644 index 0000000000..807bb589fe --- /dev/null +++ b/packages/kube-object/src/specifics/ingress-class.ts @@ -0,0 +1,61 @@ +import type { ClusterScopedMetadata } from "../api-types"; +import { KubeObject } from "../kube-object"; + +export interface IngressClassParametersReference { + /** + * For example: `"k8s.example.net"` + */ + apiGroup: string; + scope: "Namespace" | "Cluster"; + kind: "ClusterIngressParameter" | "IngressParameter"; + name: string; + /** + * The namespace for IngressClass must be defined in `spec.parameters.namespace` instead of `metadata.namespace` (!) + */ + namespace?: string; +} + +export interface IngressClassSpec { + controller: string; // "example.com/ingress-controller" + parameters?: IngressClassParametersReference; +} + +export interface IngressClassStatus {} + +export class IngressClass extends KubeObject { + static readonly kind = "IngressClass"; + + static readonly namespaced = false; + + static readonly apiBase = "/apis/networking.k8s.io/v1/ingressclasses"; + + static readonly ANNOTATION_IS_DEFAULT = "ingressclass.kubernetes.io/is-default-class"; + + getController(): string { + return this.spec.controller; + } + + getCtrlApiGroup() { + return this.spec.parameters?.apiGroup; + } + + getCtrlScope() { + return this.spec.parameters?.scope; + } + + getCtrlNs() { + return this.spec.parameters?.namespace; + } + + getCtrlKind() { + return this.spec.parameters?.kind; + } + + getCtrlName() { + return this.spec.parameters?.name; + } + + get isDefault() { + return this.metadata.annotations?.[IngressClass.ANNOTATION_IS_DEFAULT] === "true"; + } +} diff --git a/packages/kube-object/src/specifics/ingress.ts b/packages/kube-object/src/specifics/ingress.ts new file mode 100644 index 0000000000..33d8e514e4 --- /dev/null +++ b/packages/kube-object/src/specifics/ingress.ts @@ -0,0 +1,203 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import { hasTypedProperty, isString, iter } from "@k8slens/utilities"; +import type { RequireExactlyOne } from "type-fest"; +import type { TypedLocalObjectReference, NamespaceScopedMetadata } from "../api-types"; +import { KubeObject } from "../kube-object"; + +export interface ILoadBalancerIngress { + hostname?: string; + ip?: string; +} + +// extensions/v1beta1 +export interface ExtensionsBackend { + serviceName?: string; + servicePort?: number | string; +} + +// networking.k8s.io/v1 +export interface NetworkingBackend { + service?: IngressService; +} + +export type IngressBackend = (ExtensionsBackend | NetworkingBackend) & { + resource?: TypedLocalObjectReference; +}; + +export interface IngressService { + name: string; + port: RequireExactlyOne<{ + name: string; + number: number; + }>; +} + +function isExtensionsBackend(backend: IngressBackend): backend is ExtensionsBackend { + return hasTypedProperty(backend, "serviceName", isString); +} + +// eslint-disable-next-line xss/no-mixed-html +const unknownPortString = ""; + +/** + * Format an ingress backend into the name of the service and port + * @param backend The ingress target + */ +export function getBackendServiceNamePort(backend: IngressBackend | undefined): string { + if (!backend) { + return unknownPortString; + } + + if (isExtensionsBackend(backend)) { + return `${backend.serviceName}:${backend.servicePort}`; + } + + if (backend.service) { + const { name, port } = backend.service; + + return `${name}:${port.number ?? port.name}`; + } + + return unknownPortString; +} + +export interface HTTPIngressPath { + pathType: "Exact" | "Prefix" | "ImplementationSpecific"; + path?: string; + backend?: IngressBackend; +} + +export interface HTTPIngressRuleValue { + paths: HTTPIngressPath[]; +} + +export interface IngressRule { + host?: string; + http?: HTTPIngressRuleValue; +} + +export interface IngressSpec { + tls: { + secretName: string; + }[]; + rules?: IngressRule[]; + // extensions/v1beta1 + backend?: ExtensionsBackend; + /** + * The default backend which is exactly on of: + * - service + * - resource + */ + defaultBackend?: RequireExactlyOne< + NetworkingBackend & { + resource: { + apiGroup: string; + kind: string; + name: string; + }; + } + >; +} + +export interface IngressStatus { + loadBalancer: { + ingress?: ILoadBalancerIngress[]; + }; +} + +export class Ingress extends KubeObject { + static readonly kind = "Ingress"; + + static readonly namespaced = true; + + static readonly apiBase = "/apis/networking.k8s.io/v1/ingresses"; + + getRules() { + return this.spec.rules ?? []; + } + + getRoutes(): string[] { + return computeRouteDeclarations(this).map(({ url, service }) => `${url} ⇢ ${service}`); + } + + getServiceNamePort(): ExtensionsBackend | undefined { + const { spec: { backend, defaultBackend } = {} } = this; + + const serviceName = defaultBackend?.service?.name ?? backend?.serviceName; + const servicePort = + defaultBackend?.service?.port.number ?? defaultBackend?.service?.port.name ?? backend?.servicePort; + + if (!serviceName || !servicePort) { + return undefined; + } + + return { + serviceName, + servicePort, + }; + } + + getHosts() { + const { + spec: { rules = [] }, + } = this; + + return [...iter.filterMap(rules, (rule) => rule.host)]; + } + + getPorts() { + const ports: number[] = []; + const { + spec: { tls, rules = [], backend, defaultBackend }, + } = this; + const httpPort = 80; + const tlsPort = 443; + // Note: not using the port name (string) + const servicePort = defaultBackend?.service?.port.number ?? backend?.servicePort; + + if (rules.length > 0) { + if (rules.some((rule) => rule.http)) { + ports.push(httpPort); + } + } else if (servicePort !== undefined) { + ports.push(Number(servicePort)); + } + + if (tls && tls.length > 0) { + ports.push(tlsPort); + } + + return ports.join(", "); + } + + getLoadBalancers() { + return this.status?.loadBalancer?.ingress?.map((address) => address.hostname || address.ip) ?? []; + } +} + +export interface ComputedIngressRoute { + displayAsLink: boolean; + pathname: string; + url: string; + service: string; +} + +export function computeRuleDeclarations(ingress: Ingress, rule: IngressRule): ComputedIngressRoute[] { + const { host = "*", http: { paths } = { paths: [] } } = rule; + const protocol = (ingress.spec?.tls?.length ?? 0) === 0 ? "http" : "https"; + + return paths.map(({ path = "/", backend }) => ({ + displayAsLink: !host.includes("*"), + pathname: path, + url: `${protocol}://${host}${path}`, + service: getBackendServiceNamePort(backend), + })); +} + +export function computeRouteDeclarations(ingress: Ingress): ComputedIngressRoute[] { + return ingress.getRules().flatMap((rule) => computeRuleDeclarations(ingress, rule)); +} diff --git a/packages/kube-object/src/specifics/job.ts b/packages/kube-object/src/specifics/job.ts new file mode 100644 index 0000000000..5ec53c4d08 --- /dev/null +++ b/packages/kube-object/src/specifics/job.ts @@ -0,0 +1,91 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import type { LabelSelector, KubeObjectStatus, NamespaceScopedMetadata } from "../api-types"; +import { KubeObject } from "../kube-object"; +import type { Container } from "../types/container"; +import type { PodSpec } from "./pod"; + +export interface JobSpec { + parallelism?: number; + completions?: number; + backoffLimit?: number; + selector?: LabelSelector; + template: { + metadata: { + creationTimestamp?: string; + labels?: Partial>; + annotations?: Partial>; + }; + spec: PodSpec; + }; + containers?: Container[]; + restartPolicy?: string; + terminationGracePeriodSeconds?: number; + dnsPolicy?: string; + serviceAccountName?: string; + serviceAccount?: string; + schedulerName?: string; +} + +export interface JobStatus extends KubeObjectStatus { + startTime: string; + completionTime: string; + succeeded: number; +} + +export class Job extends KubeObject { + static readonly kind = "Job"; + + static readonly namespaced = true; + + static readonly apiBase = "/apis/batch/v1/jobs"; + + getSelectors(): string[] { + return KubeObject.stringifyLabels(this.spec.selector?.matchLabels); + } + + getNodeSelectors(): string[] { + return KubeObject.stringifyLabels(this.spec.template.spec.nodeSelector); + } + + getTemplateLabels(): string[] { + return KubeObject.stringifyLabels(this.spec.template.metadata.labels); + } + + getTolerations() { + return this.spec.template.spec.tolerations ?? []; + } + + getAffinity() { + return this.spec.template.spec.affinity; + } + + getAffinityNumber() { + return Object.keys(this.getAffinity() ?? {}).length; + } + + getDesiredCompletions() { + return this.spec.completions ?? 0; + } + + getCompletions() { + return this.status?.succeeded ?? 0; + } + + getParallelism() { + return this.spec.parallelism; + } + + getCondition() { + // Type of Job condition could be only Complete or Failed + // https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.21/#jobcondition-v1-batch + return this.status?.conditions?.find(({ status }) => status === "True"); + } + + getImages() { + return this.spec.template.spec.containers?.map((container) => container.image) ?? []; + } +} diff --git a/packages/kube-object/src/specifics/lease.ts b/packages/kube-object/src/specifics/lease.ts new file mode 100644 index 0000000000..e55fb821a3 --- /dev/null +++ b/packages/kube-object/src/specifics/lease.ts @@ -0,0 +1,43 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import type { NamespaceScopedMetadata } from "../api-types"; +import { KubeObject } from "../kube-object"; + +export interface LeaseSpec { + acquireTime?: string; + holderIdentity: string; + leaseDurationSeconds: number; + leaseTransitions?: number; + renewTime: string; +} + +export class Lease extends KubeObject { + static readonly kind = "Lease"; + + static readonly namespaced = true; + + static readonly apiBase = "/apis/coordination.k8s.io/v1/leases"; + + getAcquireTime(): string { + return this.spec.acquireTime || ""; + } + + getHolderIdentity(): string { + return this.spec.holderIdentity; + } + + getLeaseDurationSeconds(): number { + return this.spec.leaseDurationSeconds; + } + + getLeaseTransitions(): number | undefined { + return this.spec.leaseTransitions; + } + + getRenewTime(): string { + return this.spec.renewTime; + } +} diff --git a/packages/kube-object/src/specifics/limit-range.ts b/packages/kube-object/src/specifics/limit-range.ts new file mode 100644 index 0000000000..735cf08917 --- /dev/null +++ b/packages/kube-object/src/specifics/limit-range.ts @@ -0,0 +1,57 @@ +import type { NamespaceScopedMetadata } from "../api-types"; +import { KubeObject } from "../kube-object"; + +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +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; +} + +export interface LimitRangeSpec { + limits: LimitRangeItem[]; +} + +export class LimitRange extends KubeObject { + static readonly kind = "LimitRange"; + + static readonly namespaced = true; + + static readonly apiBase = "/api/v1/limitranges"; + + 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); + } +} diff --git a/packages/kube-object/src/specifics/mutating-webhook-configuration.ts b/packages/kube-object/src/specifics/mutating-webhook-configuration.ts new file mode 100644 index 0000000000..2a9de516e4 --- /dev/null +++ b/packages/kube-object/src/specifics/mutating-webhook-configuration.ts @@ -0,0 +1,193 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import type { + LabelSelector, + KubeJsonApiData, + KubeObjectMetadata, + KubeObjectScope, + NamespaceScopedMetadata, +} from "../api-types"; +import { KubeObject } from "../kube-object"; + +export interface WebhookClientConfig { + /** + * The location of the webhook + */ + url?: string; + + /** + * A reference to the service for this webhook. Either `service` or `url` must be specified. + */ + service?: ServiceReference; + + /** + * a PEM encoded CA bundle which will be used to validate the webhook's server certificate. + * If unspecified, system trust roots on the apiserver are used. + */ + caBundle?: string; +} + +export interface RuleWithOperations { + /** + * The API groups the resources belong to. '*' is all groups. + * If '*' is present, the length of the slice must be one. + */ + apiGroups: string[]; + + /** + * The API versions the resources belong to. '*' is all versions. + */ + apiVersions?: string[]; + + /** + * A list of resources this rule applies to. + * For example: + * - 'pods' means pods. + * - '*' means all resources, but not subresources. + * - 'pods/' means all subresources of pods. + * - '*\/scale' means all scale subresources. Allowed values are "Resource" / "Resource/Scale" / "Resource/Status". + */ + resources: string[]; + + /** + * A list of operations this rule applies to. + */ + operations: ("CREATE" | "UPDATE" | "DELETE" | "CONNECT")[]; + + /** + * The scope of this rule. + * + * @default "Cluster" + */ + scope?: "Cluster" | "Namespace"; +} + +export interface Webhook { + /** + * The name of the webhook configuration. + */ + name: string; + + /** + * Defines how to communicate with the hook. + */ + clientConfig: WebhookClientConfig; + + /** + * Rules describes what operations on what resources/subresources the webhook cares about. + * The webhook cares about an operation if it matches _any_ Rule. + */ + rules?: RuleWithOperations[]; + + /** + * An ordered list of preferred `AdmissionReview` versions the webhook expects. + * API server will try to use first version in the list which it supports. + * If none of the versions specified in this list supported by API server, validation will fail for this object. + */ + admissionReviewVersions?: string[]; + + /** + * The timeout for this webhook. + * After the timeout passes, the webhook call will be ignored + * or the API call will fail depending on the failure policy. + */ + timeoutSeconds?: number; + + /** + * Specifies how unrecognized errors from the webhook are handled + * @default "Fail" + */ + failurePolicy?: "Ignore" | "Fail"; + + /** + * Defines how the "rules" list is used to match incoming requests. + * - Exact: match a request only if it exactly matches a specified rule. + * - Equivalent: match a request if modifies a resource listed in rules, even via another API group or version. + * @default "Equivalent" + */ + matchPolicy?: "Exact" | "Equivalent"; + + // NamespaceSelector decides whether to run the webhook on an object based on whether the namespace for that object + // matches the selector. If the object itself is a namespace, the matching is performed on object.metadata.labels. + // If both the object and the webhook configuration specify namespaceSelector, they must match. + namespaceSelector?: LabelSelector; + + // ObjectSelector decides whether to run the webhook based on if the object has matching labels. + // objectSelector and namespaceSelector are ANDed. An empty objectSelector matches all objects. + // A null objectSelector matches no objects. + objectSelector?: LabelSelector; + + // SideEffects states whether this webhookk should run when no mutating or validating webhook + // needs to run. This should be false when the webhook only applies to resources that have + // the sideEffects field set to None. Defaults to true. + sideEffects?: string; + + /** + * Indicates whether this webhook should be called multiple times as part of a single admission evaluation. + */ + reinvocationPolicy?: "Never" | "IfNeeded"; +} + +export interface ServiceReference { + /** + * The namespace of the service. + */ + namespace: string; + + /** + * The name of the service. + */ + name: string; + + /** + * The URL path which will be sent in any request to this service. + */ + path?: string; + + /** + * The service port which will be used when accessing the service. + */ + port?: number | string; +} + +export interface MutatingWebhookConfigurationData + extends KubeJsonApiData, void, void> { + webhooks?: Webhook[]; +} + +export class MutatingWebhookConfiguration extends KubeObject { + static kind = "MutatingWebhookConfiguration"; + + static namespaced = false; + + static apiBase = "/apis/admissionregistration.k8s.io/v1/mutatingwebhookconfigurations"; + + webhooks?: Webhook[]; + + constructor({ webhooks, ...rest }: MutatingWebhookConfigurationData) { + super(rest); + this.webhooks = webhooks; + } + + getWebhooks(): Webhook[] { + return this.webhooks ?? []; + } + + getClientConfig(serviceName: string, serviceNamespace: string): WebhookClientConfig | undefined { + const webhooks = this.getWebhooks(); + + for (const webhook of webhooks) { + if ( + webhook.clientConfig.service?.name === serviceName && + webhook.clientConfig.service?.namespace === serviceNamespace + ) { + return webhook.clientConfig; + } + } + + return undefined; + } +} diff --git a/packages/kube-object/src/specifics/namespace.ts b/packages/kube-object/src/specifics/namespace.ts new file mode 100644 index 0000000000..525b3efced --- /dev/null +++ b/packages/kube-object/src/specifics/namespace.ts @@ -0,0 +1,44 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import type { KubeObjectStatus, ClusterScopedMetadata } from "../api-types"; +import { KubeObject } from "../kube-object"; + +export enum NamespaceStatusKind { + ACTIVE = "Active", + TERMINATING = "Terminating", +} + +export interface NamespaceSpec { + finalizers?: string[]; +} + +export interface NamespaceStatus extends KubeObjectStatus { + phase?: string; +} + +export class Namespace extends KubeObject { + static readonly kind = "Namespace"; + + static readonly namespaced = false; + + static readonly apiBase = "/api/v1/namespaces"; + + getStatus() { + return this.status?.phase ?? "-"; + } + + isSubnamespace() { + return this.getAnnotations().find((annotation) => annotation.includes("hnc.x-k8s.io/subnamespace-of")); + } + + isChildOf(parentName: string) { + return this.getLabels().find((label) => label === `${parentName}.tree.hnc.x-k8s.io/depth=1`); + } + + isControlledByHNC() { + return this.getLabels().includes("hnc.x-k8s.io/included-namespace=true"); + } +} diff --git a/packages/kube-object/src/specifics/network-policy.ts b/packages/kube-object/src/specifics/network-policy.ts new file mode 100644 index 0000000000..9c9248b75a --- /dev/null +++ b/packages/kube-object/src/specifics/network-policy.ts @@ -0,0 +1,117 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import type { LabelSelector, NamespaceScopedMetadata } from "../api-types"; +import { KubeObject } from "../kube-object"; + +export interface PolicyIpBlock { + cidr: string; + except?: string[]; +} + +export interface NetworkPolicyPort { + /** + * The protocol which network traffic must match. + * + * One of: + * - `"TCP"` + * - `"UDP"` + * - `"SCTP"` + * + * @default "TCP" + */ + protocol?: string; + + /** + * The port on the given protocol. This can either be a numerical or named + * port on a pod. If this field is not provided, this matches all port names and + * numbers. + * + * If present, only traffic on the specified protocol AND port will be matched. + */ + port?: number | string; + + /** + * If set, indicates that the range of ports from port to endPort, inclusive, + * should be allowed by the policy. This field cannot be defined if the port field + * is not defined or if the port field is defined as a named (string) port. + * + * The endPort must be equal or greater than port. + */ + endPort?: number; +} + +export interface NetworkPolicyPeer { + /** + * IPBlock defines policy on a particular IPBlock. If this field is set then + * neither of the other fields can be. + */ + ipBlock?: PolicyIpBlock; + + /** + * Selects Namespaces using cluster-scoped labels. This field follows standard label + * selector semantics; if present but empty, it selects all namespaces. + * + * If PodSelector is also set, then the NetworkPolicyPeer as a whole selects + * the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. + * + * Otherwise it selects all Pods in the Namespaces selected by NamespaceSelector. + */ + namespaceSelector?: LabelSelector; + + /** + * This is a label selector which selects Pods. This field follows standard label + * selector semantics; if present but empty, it selects all pods. + * + * If NamespaceSelector is also set, then the NetworkPolicyPeer as a whole selects + * the Pods matching PodSelector in the Namespaces selected by NamespaceSelector. + * + * Otherwise it selects the Pods matching PodSelector in the policy's own Namespace. + */ + podSelector?: LabelSelector; +} + +export interface PolicyIngress { + from?: NetworkPolicyPeer[]; + ports?: NetworkPolicyPort[]; +} + +export interface PolicyEgress { + to?: NetworkPolicyPeer[]; + ports?: NetworkPolicyPort[]; +} + +export type PolicyType = "Ingress" | "Egress"; + +export interface NetworkPolicySpec { + podSelector: LabelSelector; + policyTypes?: PolicyType[]; + ingress?: PolicyIngress[]; + egress?: PolicyEgress[]; +} + +export class NetworkPolicy extends KubeObject { + static readonly kind = "NetworkPolicy"; + + static readonly namespaced = true; + + static readonly apiBase = "/apis/networking.k8s.io/v1/networkpolicies"; + + getMatchLabels(): string[] { + if (!this.spec.podSelector || !this.spec.podSelector.matchLabels) { + return []; + } + + return Object.entries(this.spec.podSelector.matchLabels).map((data) => data.join(":")); + } + + getTypes(): string[] { + if (!this.spec.policyTypes) { + return []; + } + + return this.spec.policyTypes; + } +} diff --git a/packages/core/src/common/k8s-api/__tests__/node.test.ts b/packages/kube-object/src/specifics/node.test.ts similarity index 96% rename from packages/core/src/common/k8s-api/__tests__/node.test.ts rename to packages/kube-object/src/specifics/node.test.ts index 53ffc59d79..205c425039 100644 --- a/packages/core/src/common/k8s-api/__tests__/node.test.ts +++ b/packages/kube-object/src/specifics/node.test.ts @@ -2,12 +2,9 @@ * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ -import { Node } from "../endpoints"; -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ +import { Node } from "./node"; + describe("Node tests", () => { describe("isMasterNode()", () => { it("given a master node labelled before kubernetes 1.20, should return true", () => { diff --git a/packages/kube-object/src/specifics/node.ts b/packages/kube-object/src/specifics/node.ts new file mode 100644 index 0000000000..2b5ce77c0a --- /dev/null +++ b/packages/kube-object/src/specifics/node.ts @@ -0,0 +1,253 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import { isObject, cpuUnitsToNumber, unitsToBytes } from "@k8slens/utilities"; +import { TypedRegEx } from "typed-regex"; +import type { BaseKubeObjectCondition, ClusterScopedMetadata } from "../api-types"; +import { KubeObject } from "../kube-object"; + +export interface NodeTaint { + key: string; + value?: string; + effect: string; + timeAdded: string; +} + +export function formatNodeTaint(taint: NodeTaint): string { + if (taint.value) { + return `${taint.key}=${taint.value}:${taint.effect}`; + } + + return `${taint.key}:${taint.effect}`; +} + +export interface NodeCondition extends BaseKubeObjectCondition { + /** + * Last time we got an update on a given condition. + */ + lastHeartbeatTime?: string; +} + +/** + * These role label prefixs are the ones that are for master nodes + * + * The `master` label has been deprecated in Kubernetes 1.20, and will be removed in 1.25 so we + * have to also use the newer `control-plane` label + */ +const masterNodeLabels = ["master", "control-plane"]; + +/** + * This regex is used in the `getRoleLabels()` method bellow, but placed here + * as factoring out regexes is best practice. + */ +// eslint-disable-next-line xss/no-mixed-html +const nodeRoleLabelKeyMatcher = TypedRegEx("^.*node-role.kubernetes.io/+(?.+)$"); + +export interface NodeSpec { + podCIDR?: string; + podCIDRs?: string[]; + providerID?: string; + /** + * @deprecated see https://issues.k8s.io/61966 + */ + externalID?: string; + taints?: NodeTaint[]; + unschedulable?: boolean; +} + +export interface NodeAddress { + type: "Hostname" | "ExternalIP" | "InternalIP"; + address: string; +} + +export interface NodeStatusResources extends Partial> { + cpu?: string; + "ephemeral-storage"?: string; + "hugepages-1Gi"?: string; + "hugepages-2Mi"?: string; + memory?: string; + pods?: string; +} + +export interface ConfigMapNodeConfigSource { + kubeletConfigKey: string; + name: string; + namespace: string; + resourceVersion?: string; + uid?: string; +} + +export interface NodeConfigSource { + configMap?: ConfigMapNodeConfigSource; +} + +export interface NodeConfigStatus { + active?: NodeConfigSource; + assigned?: NodeConfigSource; + lastKnownGood?: NodeConfigSource; + error?: string; +} + +export interface DaemonEndpoint { + Port: number; //it must be uppercase for backwards compatibility +} + +export interface NodeDaemonEndpoints { + kubeletEndpoint?: DaemonEndpoint; +} + +export interface ContainerImage { + names?: string[]; + sizeBytes?: number; +} + +export interface NodeSystemInfo { + architecture: string; + bootID: string; + containerRuntimeVersion: string; + kernelVersion: string; + kubeProxyVersion: string; + kubeletVersion: string; + machineID: string; + operatingSystem: string; + osImage: string; + systemUUID: string; +} + +export interface AttachedVolume { + name: string; + devicePath: string; +} + +export interface NodeStatus { + capacity?: NodeStatusResources; + allocatable?: NodeStatusResources; + conditions?: NodeCondition[]; + addresses?: NodeAddress[]; + config?: NodeConfigStatus; + daemonEndpoints?: NodeDaemonEndpoints; + images?: ContainerImage[]; + nodeInfo?: NodeSystemInfo; + phase?: string; + volumesInUse?: string[]; + volumesAttached?: AttachedVolume[]; +} + +export class Node extends KubeObject { + static readonly kind = "Node"; + + static readonly namespaced = false; + + static readonly apiBase = "/api/v1/nodes"; + + /** + * Returns the concatination of all current condition types which have a status + * of `"True"` + */ + getNodeConditionText(): string { + if (!this.status?.conditions) { + return ""; + } + + return this.status.conditions + .filter((condition) => condition.status === "True") + .map((condition) => condition.type) + .join(" "); + } + + getTaints() { + return this.spec.taints || []; + } + + isMasterNode(): boolean { + return this.getRoleLabelItems().some((roleLabel) => masterNodeLabels.includes(roleLabel)); + } + + getRoleLabelItems(): string[] { + const { labels } = this.metadata; + const roleLabels: string[] = []; + + if (!isObject(labels)) { + return roleLabels; + } + + for (const labelKey of Object.keys(labels)) { + const match = nodeRoleLabelKeyMatcher.match(labelKey); + + if (match?.groups) { + roleLabels.push(match.groups.role); + } + } + + if (typeof labels["kubernetes.io/role"] === "string") { + roleLabels.push(labels["kubernetes.io/role"]); + } + + if (typeof labels["node.kubernetes.io/role"] === "string") { + roleLabels.push(labels["node.kubernetes.io/role"]); + } + + return roleLabels; + } + + getRoleLabels(): string { + return this.getRoleLabelItems().join(", "); + } + + getCpuCapacity() { + if (!this.status?.capacity || !this.status.capacity.cpu) { + return 0; + } + + return cpuUnitsToNumber(this.status.capacity.cpu); + } + + getMemoryCapacity() { + if (!this.status?.capacity || !this.status.capacity.memory) { + return 0; + } + + return unitsToBytes(this.status.capacity.memory); + } + + getConditions(): NodeCondition[] { + const conditions = this.status?.conditions || []; + + if (this.isUnschedulable()) { + return [{ type: "SchedulingDisabled", status: "True" }, ...conditions]; + } + + return conditions; + } + + getActiveConditions() { + return this.getConditions().filter((c) => c.status === "True"); + } + + getWarningConditions() { + const goodConditions = ["Ready", "HostUpgrades", "SchedulingDisabled"]; + + return this.getActiveConditions().filter((condition) => { + return !goodConditions.includes(condition.type); + }); + } + + getKubeletVersion() { + return this.status?.nodeInfo?.kubeletVersion ?? ""; + } + + getOperatingSystem(): string { + return ( + this.metadata?.labels?.["kubernetes.io/os"] || + this.metadata?.labels?.["beta.kubernetes.io/os"] || + this.status?.nodeInfo?.operatingSystem || + "linux" + ); + } + + isUnschedulable() { + return this.spec.unschedulable; + } +} diff --git a/packages/kube-object/src/specifics/persistent-volume-claim.ts b/packages/kube-object/src/specifics/persistent-volume-claim.ts new file mode 100644 index 0000000000..3cf3d194fa --- /dev/null +++ b/packages/kube-object/src/specifics/persistent-volume-claim.ts @@ -0,0 +1,62 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import type { Pod } from "./pod"; +import { object } from "@k8slens/utilities"; +import type { TypedLocalObjectReference, LabelSelector, NamespaceScopedMetadata } from "../api-types"; +import { KubeObject } from "../kube-object"; +import type { ResourceRequirements } from "../types/resource-requirements"; + +export interface PersistentVolumeClaimSpec { + accessModes?: string[]; + dataSource?: TypedLocalObjectReference; + dataSourceRef?: TypedLocalObjectReference; + resources?: ResourceRequirements; + selector?: LabelSelector; + storageClassName?: string; + volumeMode?: string; + volumeName?: string; +} + +export interface PersistentVolumeClaimStatus { + phase: string; // Pending +} + +export class PersistentVolumeClaim extends KubeObject< + NamespaceScopedMetadata, + PersistentVolumeClaimStatus, + PersistentVolumeClaimSpec +> { + static readonly kind = "PersistentVolumeClaim"; + + static readonly namespaced = true; + + static readonly apiBase = "/api/v1/persistentvolumeclaims"; + + getPods(pods: Pod[]): Pod[] { + return pods + .filter((pod) => pod.getNs() === this.getNs()) + .filter( + (pod) => + pod.getVolumes().filter((volume) => volume.persistentVolumeClaim?.claimName === this.getName()).length > 0, + ); + } + + getStorage(): string { + return this.spec.resources?.requests?.storage ?? "-"; + } + + getMatchLabels(): string[] { + return object.entries(this.spec.selector?.matchLabels).map(([name, val]) => `${name}:${val}`); + } + + getMatchExpressions() { + return this.spec.selector?.matchExpressions ?? []; + } + + getStatus(): string { + return this.status?.phase ?? "-"; + } +} diff --git a/packages/kube-object/src/specifics/persistent-volume.ts b/packages/kube-object/src/specifics/persistent-volume.ts new file mode 100644 index 0000000000..eacbd51125 --- /dev/null +++ b/packages/kube-object/src/specifics/persistent-volume.ts @@ -0,0 +1,101 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import { unitsToBytes } from "@k8slens/utilities"; +import type { TypedLocalObjectReference, LabelSelector, ObjectReference, ClusterScopedMetadata } from "../api-types"; +import { KubeObject } from "../kube-object"; +import type { ResourceRequirements } from "../types/resource-requirements"; + +export interface PersistentVolumeSpec { + /** + * AccessModes contains the desired access modes the volume should have. + * + * More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1 + */ + accessModes?: string[]; + dataSource?: TypedLocalObjectReference; + dataSourceRef?: TypedLocalObjectReference; + resources?: ResourceRequirements; + selector?: LabelSelector; + + /** + * Name of the StorageClass required by the claim. + * + * More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1 + */ + storageClassName?: string; + + /** + * Defines what type of volume is required by the claim. Value of Filesystem is implied when not + * included in claim spec. + */ + volumeMode?: string; + + /** + * A description of the persistent volume\'s resources and capacity. + * + * More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#capacity + */ + capacity?: Partial>; + flexVolume?: { + driver: string; // ceph.rook.io/rook-ceph-system, + options?: { + clusterNamespace: string; // rook-ceph, + image: string; // pvc-c5d7c485-9f1b-11e8-b0ea-9600000e54fb, + pool: string; // replicapool, + storageClass: string; // rook-ceph-block + }; + }; + mountOptions?: string[]; + claimRef?: ObjectReference; + persistentVolumeReclaimPolicy?: string; // Delete, + nfs?: { + path: string; + server: string; + }; +} + +export interface PersistentVolumeStatus { + phase: string; + reason?: string; +} + +export class PersistentVolume extends KubeObject { + static kind = "PersistentVolume"; + + static namespaced = false; + + static apiBase = "/api/v1/persistentvolumes"; + + getCapacity(inBytes = false) { + const capacity = this.spec.capacity; + + if (capacity?.storage) { + if (inBytes) { + return unitsToBytes(capacity.storage); + } + + return capacity.storage; + } + + return 0; + } + + getStatus() { + return this.status?.phase || "-"; + } + + getStorageClass(): string { + return this.spec.storageClassName ?? ""; + } + + getClaimRefName(): string { + return this.spec.claimRef?.name ?? ""; + } + + getStorageClassName() { + return this.spec.storageClassName || ""; + } +} diff --git a/packages/kube-object/src/specifics/pod-disruption-budget.ts b/packages/kube-object/src/specifics/pod-disruption-budget.ts new file mode 100644 index 0000000000..05d1a8a359 --- /dev/null +++ b/packages/kube-object/src/specifics/pod-disruption-budget.ts @@ -0,0 +1,73 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import type { LabelSelector, NamespaceScopedMetadata } from "../api-types"; +import { KubeObject } from "../kube-object"; +import type { Condition } from "../types/condition"; + +export interface V1Beta1PodDisruptionBudgetSpec { + minAvailable: string; + maxUnavailable: string; + selector: LabelSelector; +} + +export interface V1PodDisruptionBudgetSpec { + maxUnavailable?: string | number; + minAvailable?: string | number; + selector?: LabelSelector; +} + +export type PodDisruptionBudgetSpec = V1Beta1PodDisruptionBudgetSpec | V1PodDisruptionBudgetSpec; + +export interface V1Beta1PodDisruptionBudgetStatus { + currentHealthy: number; + desiredHealthy: number; + disruptionsAllowed: number; + expectedPods: number; +} + +export interface V1PodDisruptionBudgetStatus { + conditions?: Condition[]; + currentHealthy: number; + desiredHealthy: number; + disruptedPods?: Partial>; + disruptionsAllowed: number; + expectedPods: number; + observedGeneration?: number; +} + +export type PodDisruptionBudgetStatus = V1Beta1PodDisruptionBudgetStatus | V1PodDisruptionBudgetStatus; + +export class PodDisruptionBudget extends KubeObject< + NamespaceScopedMetadata, + PodDisruptionBudgetStatus, + PodDisruptionBudgetSpec +> { + static readonly kind = "PodDisruptionBudget"; + + static readonly namespaced = true; + + static readonly apiBase = "/apis/policy/v1beta1/poddisruptionbudgets"; + + getSelectors() { + return KubeObject.stringifyLabels(this.spec.selector?.matchLabels); + } + + getMinAvailable() { + return this.spec.minAvailable ?? "N/A"; + } + + getMaxUnavailable() { + return this.spec.maxUnavailable ?? "N/A"; + } + + getCurrentHealthy() { + return this.status?.currentHealthy ?? 0; + } + + getDesiredHealthy() { + return this.status?.desiredHealthy ?? 0; + } +} diff --git a/packages/kube-object/src/specifics/pod-metrics.ts b/packages/kube-object/src/specifics/pod-metrics.ts new file mode 100644 index 0000000000..0f70d8678b --- /dev/null +++ b/packages/kube-object/src/specifics/pod-metrics.ts @@ -0,0 +1,44 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import type { KubeJsonApiData, KubeObjectMetadata, KubeObjectScope, NamespaceScopedMetadata } from "../api-types"; +import { KubeObject } from "../kube-object"; + +export interface PodMetricsData extends KubeJsonApiData, void, void> { + timestamp: string; + window: string; + containers: PodMetricsContainer[]; +} + +export interface PodMetricsContainerUsage { + cpu: string; + memory: string; +} + +export interface PodMetricsContainer { + name: string; + usage: PodMetricsContainerUsage; +} + +export class PodMetrics extends KubeObject { + static readonly kind = "PodMetrics"; + + static readonly namespaced = true; + + static readonly apiBase = "/apis/metrics.k8s.io/v1beta1/pods"; + + timestamp: string; + + window: string; + + containers: PodMetricsContainer[]; + + constructor({ timestamp, window, containers, ...rest }: PodMetricsData) { + super(rest); + this.timestamp = timestamp; + this.window = window; + this.containers = containers; + } +} diff --git a/packages/kube-object/src/specifics/pod-security-policy.ts b/packages/kube-object/src/specifics/pod-security-policy.ts new file mode 100644 index 0000000000..9d59f93a0d --- /dev/null +++ b/packages/kube-object/src/specifics/pod-security-policy.ts @@ -0,0 +1,107 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import type { ClusterScopedMetadata } from "../api-types"; +import { KubeObject } from "../kube-object"; + +export interface PodSecurityPolicySpec { + allowPrivilegeEscalation?: boolean; + allowedCSIDrivers?: { + name: string; + }[]; + allowedCapabilities: string[]; + allowedFlexVolumes?: { + driver: string; + }[]; + allowedHostPaths?: { + pathPrefix: string; + readOnly: boolean; + }[]; + allowedProcMountTypes?: string[]; + allowedUnsafeSysctls?: string[]; + defaultAddCapabilities?: string[]; + defaultAllowPrivilegeEscalation?: boolean; + forbiddenSysctls?: string[]; + fsGroup?: { + rule: string; + ranges: { + max: number; + min: number; + }[]; + }; + hostIPC?: boolean; + hostNetwork?: boolean; + hostPID?: boolean; + hostPorts?: { + max: number; + min: number; + }[]; + privileged?: boolean; + readOnlyRootFilesystem?: boolean; + requiredDropCapabilities?: string[]; + runAsGroup?: { + ranges: { + max: number; + min: number; + }[]; + rule: string; + }; + runAsUser?: { + rule: string; + ranges: { + max: number; + min: number; + }[]; + }; + runtimeClass?: { + allowedRuntimeClassNames: string[]; + defaultRuntimeClassName: string; + }; + seLinux?: { + rule: string; + seLinuxOptions: { + level: string; + role: string; + type: string; + user: string; + }; + }; + supplementalGroups?: { + rule: string; + ranges: { + max: number; + min: number; + }[]; + }; + volumes?: string[]; +} + +export class PodSecurityPolicy extends KubeObject { + static readonly kind = "PodSecurityPolicy"; + + static readonly namespaced = false; + + static readonly apiBase = "/apis/policy/v1beta1/podsecuritypolicies"; + + isPrivileged() { + return !!this.spec.privileged; + } + + getVolumes() { + return this.spec.volumes || []; + } + + getRules() { + const { fsGroup, runAsGroup, runAsUser, supplementalGroups, seLinux } = this.spec; + + return { + fsGroup: fsGroup ? fsGroup.rule : "", + runAsGroup: runAsGroup ? runAsGroup.rule : "", + runAsUser: runAsUser ? runAsUser.rule : "", + supplementalGroups: supplementalGroups ? supplementalGroups.rule : "", + seLinux: seLinux ? seLinux.rule : "", + }; + } +} diff --git a/packages/core/src/common/k8s-api/__tests__/pods.test.ts b/packages/kube-object/src/specifics/pod.test.ts similarity index 73% rename from packages/core/src/common/k8s-api/__tests__/pods.test.ts rename to packages/kube-object/src/specifics/pod.test.ts index 3ed979face..9302b42ef0 100644 --- a/packages/core/src/common/k8s-api/__tests__/pods.test.ts +++ b/packages/kube-object/src/specifics/pod.test.ts @@ -4,8 +4,8 @@ */ import assert from "assert"; -import type { Container, PodContainerStatus } from "../endpoints"; -import { Pod } from "../endpoints"; +import type { Container } from "../types/container"; +import { Pod, type PodContainerStatus } from "./pod"; interface GetDummyPodOptions { running?: number; @@ -15,12 +15,7 @@ interface GetDummyPodOptions { } function getDummyPod(rawOpts: GetDummyPodOptions = {}): Pod { - const { - running = 0, - dead = 0, - initDead = 0, - initRunning = 0, - } = rawOpts; + const { running = 0, dead = 0, initDead = 0, initRunning = 0 } = rawOpts; const containers: Container[] = []; const initContainers: Container[] = []; @@ -92,7 +87,7 @@ function getDummyPod(rawOpts: GetDummyPodOptions = {}): Pod { state: { terminated: { startedAt: "before", - exitCode: i+1, + exitCode: i + 1, finishedAt: "later", reason: `reason_${i}`, }, @@ -139,7 +134,7 @@ function getDummyPod(rawOpts: GetDummyPodOptions = {}): Pod { state: { terminated: { startedAt: "before", - exitCode: i+1, + exitCode: i + 1, finishedAt: "later", reason: `reason_${i}`, }, @@ -163,55 +158,58 @@ describe("Pods", () => { } } - describe.each(podTests)("for [%d running, %d dead] & initial [%d running, %d dead]", (running, dead, initRunning, initDead) => { - const pod = getDummyPod({ running, dead, initRunning, initDead }); + describe.each(podTests)( + "for [%d running, %d dead] & initial [%d running, %d dead]", + (running, dead, initRunning, initDead) => { + const pod = getDummyPod({ running, dead, initRunning, initDead }); - function getNamedContainer(name: string) { - return { - image: "dummy", - imagePullPolicy: "Always", - name, - }; - } - - it("getRunningContainers should return only running and init running", () => { - const res = [ - ...Array.from(new Array(running), (val, index) => getNamedContainer(`container_running_${index}`)), - ...Array.from(new Array(initRunning), (val, index) => getNamedContainer(`container_init-running_${index}`)), - ]; - - expect(pod.getRunningContainers()).toStrictEqual(res); - }); - - it("getAllContainers should return all containers", () => { - const res = [ - ...Array.from(new Array(running), (val, index) => getNamedContainer(`container_running_${index}`)), - ...Array.from(new Array(dead), (val, index) => getNamedContainer(`container_dead_${index}`)), - ...Array.from(new Array(initRunning), (val, index) => getNamedContainer(`container_init-running_${index}`)), - ...Array.from(new Array(initDead), (val, index) => getNamedContainer(`container_init-dead_${index}`)), - ]; - - expect(pod.getAllContainers()).toStrictEqual(res); - }); - - it("getRestartsCount should return total restart counts", () => { - function sum(len: number): number { - let res = 0; - - for (let i = 0; i < len; i += 1) { - res += i; - } - - return res; + function getNamedContainer(name: string) { + return { + image: "dummy", + imagePullPolicy: "Always", + name, + }; } - expect(pod.getRestartsCount()).toStrictEqual(sum(running) + sum(dead)); - }); + it("getRunningContainers should return only running and init running", () => { + const res = [ + ...Array.from(new Array(running), (val, index) => getNamedContainer(`container_running_${index}`)), + ...Array.from(new Array(initRunning), (val, index) => getNamedContainer(`container_init-running_${index}`)), + ]; - it("hasIssues should return false", () => { - expect(pod.hasIssues()).toStrictEqual(false); - }); - }); + expect(pod.getRunningContainers()).toStrictEqual(res); + }); + + it("getAllContainers should return all containers", () => { + const res = [ + ...Array.from(new Array(running), (val, index) => getNamedContainer(`container_running_${index}`)), + ...Array.from(new Array(dead), (val, index) => getNamedContainer(`container_dead_${index}`)), + ...Array.from(new Array(initRunning), (val, index) => getNamedContainer(`container_init-running_${index}`)), + ...Array.from(new Array(initDead), (val, index) => getNamedContainer(`container_init-dead_${index}`)), + ]; + + expect(pod.getAllContainers()).toStrictEqual(res); + }); + + it("getRestartsCount should return total restart counts", () => { + function sum(len: number): number { + let res = 0; + + for (let i = 0; i < len; i += 1) { + res += i; + } + + return res; + } + + expect(pod.getRestartsCount()).toStrictEqual(sum(running) + sum(dead)); + }); + + it("hasIssues should return false", () => { + expect(pod.hasIssues()).toStrictEqual(false); + }); + }, + ); describe("getSelectedNodeOs", () => { it("should return stable", () => { diff --git a/packages/kube-object/src/specifics/pod.ts b/packages/kube-object/src/specifics/pod.ts new file mode 100644 index 0000000000..3025e701ce --- /dev/null +++ b/packages/kube-object/src/specifics/pod.ts @@ -0,0 +1,834 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import { isDefined } from "@k8slens/utilities"; +import type { RequireExactlyOne } from "type-fest"; +import type { PersistentVolumeClaimSpec } from "./persistent-volume-claim"; +import type { + Affinity, + KubeObjectMetadata, + LocalObjectReference, + NamespaceScopedMetadata, + Toleration, +} from "../api-types"; +import { KubeObject } from "../kube-object"; +import type { Container, ObjectFieldSelector, PodSecurityContext, Probe, ResourceFieldSelector } from "../types"; +import type { SecretReference } from "./secret"; + +// Reference: https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#read-log-pod-v1-core +export interface PodLogsQuery { + container?: string; + tailLines?: number; + timestamps?: boolean; + sinceTime?: string; // Date.toISOString()-format + follow?: boolean; + previous?: boolean; +} + +export enum PodStatusPhase { + TERMINATED = "Terminated", + FAILED = "Failed", + PENDING = "Pending", + RUNNING = "Running", + SUCCEEDED = "Succeeded", + EVICTED = "Evicted", +} + +export interface ContainerStateRunning { + startedAt: string; +} + +export interface ContainerStateWaiting { + reason: string; + message: string; +} + +export interface ContainerStateTerminated { + startedAt: string; + finishedAt: string; + exitCode: number; + reason: string; + containerID?: string; + message?: string; + signal?: number; +} + +/** + * ContainerState holds a possible state of container. Only one of its members + * may be specified. If none of them is specified, the default one is + * `ContainerStateWaiting`. + */ +export interface ContainerState { + running?: ContainerStateRunning; + waiting?: ContainerStateWaiting; + terminated?: ContainerStateTerminated; +} + +export type ContainerStateValues = Partial; + +export interface PodContainerStatus { + name: string; + state?: ContainerState; + lastState?: ContainerState; + ready: boolean; + restartCount: number; + image: string; + imageID: string; + containerID?: string; + started?: boolean; +} + +export interface AwsElasticBlockStoreSource { + volumeID: string; + fsType: string; +} + +export interface AzureDiskSource { + /** + * The name of the VHD blob object OR the name of an Azure managed data disk if `kind` is `"Managed"`. + */ + diskName: string; + /** + * The URI of the vhd blob object OR the `resourceID` of an Azure managed data disk if `kind` is `"Managed"`. + */ + diskURI: string; + /** + * Kind of disk + * @default "Shared" + */ + kind?: "Shared" | "Dedicated" | "Managed"; + /** + * Disk caching mode. + * @default "None" + */ + cachingMode?: "None" | "ReadOnly" | "ReadWrite"; + /** + * The filesystem type to mount. + * @default "ext4" + */ + fsType?: string; + /** + * Whether the filesystem is used as readOnly. + * @default false + */ + readonly?: boolean; +} + +export interface AzureFileSource { + /** + * The name of the secret that contains both Azure storage account name and key. + */ + secretName: string; + /** + * The share name to be used. + */ + shareName: string; + /** + * In case the secret is stored in a different namespace. + * @default "default" + */ + secretNamespace?: string; + /** + * Whether the filesystem is used as readOnly. + */ + readOnly: boolean; +} + +export interface CephfsSource { + /** + * List of Ceph monitors + */ + monitors: string[]; + /** + * Used as the mounted root, rather than the full Ceph tree. + * @default "/" + */ + path?: string; + /** + * The RADOS user name. + * @default "admin" + */ + user?: string; + /** + * The path to the keyring file. + * @default "/etc/ceph/user.secret" + */ + secretFile?: string; + /** + * Reference to Ceph authentication secrets. If provided, then the secret overrides `secretFile` + */ + secretRef?: SecretReference; + /** + * Whether the filesystem is used as readOnly. + * + * @default false + */ + readOnly?: boolean; +} + +export interface CinderSource { + volumeID: string; + fsType: string; + /** + * @default false + */ + readOnly?: boolean; + secretRef?: SecretReference; +} + +export interface ConfigMapSource { + name: string; + items: { + key: string; + path: string; + }[]; +} + +export interface DownwardApiSource { + items: { + path: string; + fieldRef: { + fieldPath: string; + }; + }[]; +} + +export interface EphemeralSource { + volumeClaimTemplate: { + /** + * All the rest of the fields are ignored and rejected during validation + */ + metadata?: Pick; + spec: PersistentVolumeClaimSpec; + }; +} + +export interface EmptyDirSource { + medium?: string; + sizeLimit?: string; +} + +export interface FiberChannelSource { + /** + * A list of World Wide Names + */ + targetWWNs: string[]; + /** + * Logical Unit number + */ + lun: number; + /** + * The type of filesystem + * @default "ext4" + */ + fsType?: string; + readOnly: boolean; +} + +export interface FlockerSource { + datasetName: string; +} + +export interface FlexVolumeSource { + driver: string; + fsType?: string; + secretRef?: LocalObjectReference; + /** + * @default false + */ + readOnly?: boolean; + options?: Record; +} + +export interface GcePersistentDiskSource { + pdName: string; + fsType: string; +} + +export interface GitRepoSource { + repository: string; + revision: string; +} + +export interface GlusterFsSource { + /** + * The name of the Endpoints object that represents a Gluster cluster configuration. + */ + endpoints: string; + /** + * The Glusterfs volume name. + */ + path: string; + /** + * The boolean that sets the mountpoint readOnly or readWrite. + */ + readOnly: boolean; +} + +export interface HostPathSource { + path: string; + /** + * Determines the sorts of checks that will be done + * @default "" + */ + type?: "" | "DirectoryOrCreate" | "Directory" | "FileOrCreate" | "File" | "Socket" | "CharDevice" | "BlockDevice"; +} + +export interface IScsiSource { + targetPortal: string; + iqn: string; + lun: number; + fsType: string; + readOnly: boolean; + chapAuthDiscovery?: boolean; + chapAuthSession?: boolean; + secretRef?: SecretReference; +} + +export interface LocalSource { + path: string; +} + +export interface NetworkFsSource { + server: string; + path: string; + readOnly?: boolean; +} + +export interface PersistentVolumeClaimSource { + claimName: string; +} + +export interface PhotonPersistentDiskSource { + pdID: string; + /** + * @default "ext4" + */ + fsType?: string; +} + +export interface PortworxVolumeSource { + volumeID: string; + fsType?: string; + readOnly?: boolean; +} + +export interface KeyToPath { + key: string; + path: string; + mode?: number; +} + +export interface ConfigMapProjection { + name: string; + items?: KeyToPath[]; + optional?: boolean; +} + +export interface DownwardAPIVolumeFile { + path: string; + fieldRef?: ObjectFieldSelector; + resourceFieldRef?: ResourceFieldSelector; + mode?: number; +} + +export interface DownwardAPIProjection { + items?: DownwardAPIVolumeFile[]; +} + +export interface SecretProjection { + name: string; + items?: KeyToPath[]; + optional?: boolean; +} + +export interface ServiceAccountTokenProjection { + audience?: string; + expirationSeconds?: number; + path: string; +} + +export interface VolumeProjection { + secret?: SecretProjection; + downwardAPI?: DownwardAPIProjection; + configMap?: ConfigMapProjection; + serviceAccountToken?: ServiceAccountTokenProjection; +} + +export interface ProjectedSource { + sources?: VolumeProjection[]; + defaultMode?: number; +} + +export interface QuobyteSource { + registry: string; + volume: string; + /** + * @default false + */ + readOnly?: boolean; + /** + * @default "serivceaccount" + */ + user?: string; + group?: string; + tenant?: string; +} + +export interface RadosBlockDeviceSource { + monitors: string[]; + image: string; + /** + * @default "ext4" + */ + fsType?: string; + /** + * @default "rbd" + */ + pool?: string; + /** + * @default "admin" + */ + user?: string; + /** + * @default "/etc/ceph/keyring" + */ + keyring?: string; + secretRef?: SecretReference; + /** + * @default false + */ + readOnly?: boolean; +} + +export interface ScaleIoSource { + gateway: string; + system: string; + secretRef?: LocalObjectReference; + /** + * @default false + */ + sslEnabled?: boolean; + protectionDomain?: string; + storagePool?: string; + /** + * @default "ThinProvisioned" + */ + storageMode?: "ThickProvisioned" | "ThinProvisioned"; + volumeName: string; + /** + * @default "xfs" + */ + fsType?: string; + /** + * @default false + */ + readOnly?: boolean; +} + +export interface SecretSource { + secretName: string; + items?: { + key: string; + path: string; + mode?: number; + }[]; + defaultMode?: number; + optional?: boolean; +} + +export interface StorageOsSource { + volumeName: string; + /** + * @default Pod.metadata.namespace + */ + volumeNamespace?: string; + /** + * @default "ext4" + */ + fsType?: string; + /** + * @default false + */ + readOnly?: boolean; + secretRef?: LocalObjectReference; +} + +export interface VsphereVolumeSource { + volumePath: string; + /** + * @default "ext4" + */ + fsType?: string; + storagePolicyName?: string; + storagePolicyID?: string; +} + +export interface ContainerStorageInterfaceSource { + driver: string; + /** + * @default false + */ + readOnly?: boolean; + /** + * @default "ext4" + */ + fsType?: string; + volumeAttributes?: Record; + controllerPublishSecretRef?: SecretReference; + nodeStageSecretRef?: SecretReference; + nodePublishSecretRef?: SecretReference; + controllerExpandSecretRef?: SecretReference; +} + +export interface PodVolumeVariants { + awsElasticBlockStore: AwsElasticBlockStoreSource; + azureDisk: AzureDiskSource; + azureFile: AzureFileSource; + cephfs: CephfsSource; + cinder: CinderSource; + configMap: ConfigMapSource; + csi: ContainerStorageInterfaceSource; + downwardAPI: DownwardApiSource; + emptyDir: EmptyDirSource; + ephemeral: EphemeralSource; + fc: FiberChannelSource; + flexVolume: FlexVolumeSource; + flocker: FlockerSource; + gcePersistentDisk: GcePersistentDiskSource; + gitRepo: GitRepoSource; + glusterfs: GlusterFsSource; + hostPath: HostPathSource; + iscsi: IScsiSource; + local: LocalSource; + nfs: NetworkFsSource; + persistentVolumeClaim: PersistentVolumeClaimSource; + photonPersistentDisk: PhotonPersistentDiskSource; + portworxVolume: PortworxVolumeSource; + projected: ProjectedSource; + quobyte: QuobyteSource; + rbd: RadosBlockDeviceSource; + scaleIO: ScaleIoSource; + secret: SecretSource; + storageos: StorageOsSource; + vsphereVolume: VsphereVolumeSource; +} + +/** + * The valid kinds of volume + */ +export type PodVolumeKind = keyof PodVolumeVariants; + +export type PodSpecVolume = RequireExactlyOne & { + name: string; +}; + +export interface HostAlias { + ip: string; + hostnames: string[]; +} + +export interface Sysctl { + name: string; + value: string; +} + +export interface TopologySpreadConstraint {} + +export interface PodSpec { + activeDeadlineSeconds?: number; + affinity?: Affinity; + automountServiceAccountToken?: boolean; + containers?: Container[]; + dnsPolicy?: string; + enableServiceLinks?: boolean; + ephemeralContainers?: unknown[]; + hostAliases?: HostAlias[]; + hostIPC?: boolean; + hostname?: string; + hostNetwork?: boolean; + hostPID?: boolean; + imagePullSecrets?: LocalObjectReference[]; + initContainers?: Container[]; + nodeName?: string; + nodeSelector?: Partial>; + overhead?: Partial>; + preemptionPolicy?: string; + priority?: number; + priorityClassName?: string; + readinessGates?: unknown[]; + restartPolicy?: string; + runtimeClassName?: string; + schedulerName?: string; + securityContext?: PodSecurityContext; + serviceAccount?: string; + serviceAccountName?: string; + setHostnameAsFQDN?: boolean; + shareProcessNamespace?: boolean; + subdomain?: string; + terminationGracePeriodSeconds?: number; + tolerations?: Toleration[]; + topologySpreadConstraints?: TopologySpreadConstraint[]; + volumes?: PodSpecVolume[]; +} + +export interface PodCondition { + lastProbeTime?: number; + lastTransitionTime?: string; + message?: string; + reason?: string; + type: string; + status: string; +} + +export interface PodStatus { + phase: string; + conditions: PodCondition[]; + hostIP: string; + podIP: string; + podIPs?: { + ip: string; + }[]; + startTime: string; + initContainerStatuses?: PodContainerStatus[]; + containerStatuses?: PodContainerStatus[]; + qosClass?: string; + reason?: string; +} + +export class Pod extends KubeObject { + static kind = "Pod"; + + static namespaced = true; + + static apiBase = "/api/v1/pods"; + + getAffinityNumber() { + return Object.keys(this.getAffinity()).length; + } + + getInitContainers() { + return this.spec?.initContainers ?? []; + } + + getContainers() { + return this.spec?.containers ?? []; + } + + getAllContainers() { + return [...this.getContainers(), ...this.getInitContainers()]; + } + + getRunningContainers() { + const runningContainerNames = new Set( + this.getContainerStatuses() + .filter(({ state }) => state?.running) + .map(({ name }) => name), + ); + + return this.getAllContainers().filter(({ name }) => runningContainerNames.has(name)); + } + + getContainerStatuses(includeInitContainers = true): PodContainerStatus[] { + const { containerStatuses = [], initContainerStatuses = [] } = this.status ?? {}; + + if (includeInitContainers) { + return [...containerStatuses, ...initContainerStatuses]; + } + + return [...containerStatuses]; + } + + getRestartsCount(): number { + const { containerStatuses = [] } = this.status ?? {}; + + return containerStatuses.reduce((totalCount, { restartCount }) => totalCount + restartCount, 0); + } + + getQosClass() { + return this.status?.qosClass || ""; + } + + getReason() { + return this.status?.reason || ""; + } + + getPriorityClassName() { + return this.spec?.priorityClassName || ""; + } + + getRuntimeClassName() { + return this.spec?.runtimeClassName || ""; + } + + getServiceAccountName() { + return this.spec?.serviceAccountName || ""; + } + + getStatus(): PodStatusPhase { + const phase = this.getStatusPhase(); + const reason = this.getReason(); + const trueConditionTypes = new Set( + this.getConditions() + .filter(({ status }) => status === "True") + .map(({ type }) => type), + ); + const isInGoodCondition = ["Initialized", "Ready"].every((condition) => trueConditionTypes.has(condition)); + + if (reason === PodStatusPhase.EVICTED) { + return PodStatusPhase.EVICTED; + } + + if (phase === PodStatusPhase.FAILED) { + return PodStatusPhase.FAILED; + } + + if (phase === PodStatusPhase.SUCCEEDED) { + return PodStatusPhase.SUCCEEDED; + } + + if (phase === PodStatusPhase.RUNNING && isInGoodCondition) { + return PodStatusPhase.RUNNING; + } + + return PodStatusPhase.PENDING; + } + + // Returns pod phase or container error if occurred + getStatusMessage(): string { + if (this.getReason() === PodStatusPhase.EVICTED) { + return "Evicted"; + } + + if (this.metadata.deletionTimestamp) { + return "Terminating"; + } + + return this.getStatusPhase() || "Waiting"; + } + + getStatusPhase() { + return this.status?.phase; + } + + getConditions() { + return this.status?.conditions ?? []; + } + + getVolumes() { + return this.spec?.volumes ?? []; + } + + getSecrets(): string[] { + return this.getVolumes() + .map((vol) => vol.secret?.secretName) + .filter(isDefined); + } + + getNodeSelectors(): string[] { + return Object.entries(this.spec?.nodeSelector ?? {}).map((values) => values.join(": ")); + } + + getTolerations() { + return this.spec?.tolerations ?? []; + } + + getAffinity(): Affinity { + return this.spec?.affinity ?? {}; + } + + hasIssues() { + for (const { type, status } of this.getConditions()) { + if (type === "Ready" && status !== "True") { + return true; + } + } + + for (const { state } of this.getContainerStatuses()) { + if (state?.waiting?.reason === "CrashLookBackOff") { + return true; + } + } + + return this.getStatusPhase() !== "Running"; + } + + getLivenessProbe(container: Container) { + return this.getProbe(container, container.livenessProbe); + } + + getReadinessProbe(container: Container) { + return this.getProbe(container, container.readinessProbe); + } + + getStartupProbe(container: Container) { + return this.getProbe(container, container.startupProbe); + } + + private getProbe(container: Container, probe: Probe | undefined): string[] { + const probeItems: string[] = []; + + if (!probe) { + return probeItems; + } + + const { + httpGet, + exec, + tcpSocket, + initialDelaySeconds = 0, + timeoutSeconds = 0, + periodSeconds = 0, + successThreshold = 0, + failureThreshold = 0, + } = probe; + + // HTTP Request + if (httpGet) { + const { path = "", port, host = "", scheme = "HTTP" } = httpGet; + const resolvedPort = + typeof port === "number" + ? port + : // Try and find the port number associated witht the name or fallback to the name itself + container.ports?.find((containerPort) => containerPort.name === port)?.containerPort || port; + + probeItems.push("http-get", `${scheme.toLowerCase()}://${host}:${resolvedPort}${path}`); + } + + // Command + if (exec?.command) { + probeItems.push(`exec [${exec.command.join(" ")}]`); + } + + // TCP Probe + if (tcpSocket?.port) { + probeItems.push(`tcp-socket :${tcpSocket.port}`); + } + + probeItems.push( + `delay=${initialDelaySeconds}s`, + `timeout=${timeoutSeconds}s`, + `period=${periodSeconds}s`, + `#success=${successThreshold}`, + `#failure=${failureThreshold}`, + ); + + return probeItems; + } + + getNodeName(): string | undefined { + return this.spec?.nodeName; + } + + getSelectedNodeOs(): string | undefined { + return this.spec?.nodeSelector?.["kubernetes.io/os"] || this.spec?.nodeSelector?.["beta.kubernetes.io/os"]; + } + + getIPs(): string[] { + const podIPs = this.status?.podIPs ?? []; + + return podIPs.map((value) => value.ip); + } +} diff --git a/packages/kube-object/src/specifics/priority-class.ts b/packages/kube-object/src/specifics/priority-class.ts new file mode 100644 index 0000000000..cae01e230c --- /dev/null +++ b/packages/kube-object/src/specifics/priority-class.ts @@ -0,0 +1,55 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import type { KubeJsonApiData, KubeObjectMetadata, KubeObjectScope, ClusterScopedMetadata } from "../api-types"; +import { KubeObject } from "../kube-object"; +import type { PreemptionPolicy } from "../types/preemption-policy"; + +export interface PriorityClassData extends KubeJsonApiData, void, void> { + description?: string; + globalDefault?: boolean; + preemptionPolicy?: PreemptionPolicy; + value: number; +} + +export class PriorityClass extends KubeObject { + static readonly kind = "PriorityClass"; + + static readonly namespaced = false; + + static readonly apiBase = "/apis/scheduling.k8s.io/v1/priorityclasses"; + + description?: string; + + globalDefault?: boolean; + + preemptionPolicy?: PreemptionPolicy; + + value?: number; + + constructor({ description, globalDefault, preemptionPolicy, value, ...rest }: PriorityClassData) { + super(rest); + this.description = description; + this.globalDefault = globalDefault; + this.preemptionPolicy = preemptionPolicy; + this.value = value; + } + + getDescription() { + return this.description || ""; + } + + getGlobalDefault() { + return (this.globalDefault || false).toString(); + } + + getPreemptionPolicy() { + return this.preemptionPolicy || "PreemptLowerPriority"; + } + + getValue() { + return this.value; + } +} diff --git a/packages/kube-object/src/specifics/replica-set.ts b/packages/kube-object/src/specifics/replica-set.ts new file mode 100644 index 0000000000..e1d2ead06a --- /dev/null +++ b/packages/kube-object/src/specifics/replica-set.ts @@ -0,0 +1,73 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import type { LabelSelector, KubeObjectStatus, NamespaceScopedMetadata } from "../api-types"; +import { KubeObject } from "../kube-object"; +import type { PodTemplateSpec } from "../types/pod-template-spec"; + +export interface ReplicaSetSpec { + replicas?: number; + selector: LabelSelector; + template?: PodTemplateSpec; + minReadySeconds?: number; +} + +export interface ReplicaSetStatus extends KubeObjectStatus { + replicas: number; + fullyLabeledReplicas?: number; + readyReplicas?: number; + availableReplicas?: number; + observedGeneration?: number; +} + +export class ReplicaSet extends KubeObject { + static kind = "ReplicaSet"; + + static namespaced = true; + + static apiBase = "/apis/apps/v1/replicasets"; + + getSelectors(): string[] { + return KubeObject.stringifyLabels(this.spec.selector.matchLabels); + } + + getNodeSelectors(): string[] { + return KubeObject.stringifyLabels(this.spec.template?.spec?.nodeSelector); + } + + getTemplateLabels(): string[] { + return KubeObject.stringifyLabels(this.spec.template?.metadata?.labels); + } + + getTolerations() { + return this.spec.template?.spec?.tolerations ?? []; + } + + getAffinity() { + return this.spec.template?.spec?.affinity; + } + + getAffinityNumber() { + return Object.keys(this.getAffinity() ?? {}).length; + } + + getDesired() { + return this.spec.replicas ?? 0; + } + + getCurrent() { + return this.status?.availableReplicas ?? 0; + } + + getReady() { + return this.status?.readyReplicas ?? 0; + } + + getImages() { + const containers = this.spec.template?.spec?.containers ?? []; + + return containers.map((container) => container.image); + } +} diff --git a/packages/kube-object/src/specifics/replication-controller.ts b/packages/kube-object/src/specifics/replication-controller.ts new file mode 100644 index 0000000000..4c9876b11e --- /dev/null +++ b/packages/kube-object/src/specifics/replication-controller.ts @@ -0,0 +1,122 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import type { + KubeObjectMetadata, + KubeObjectStatus, + NamespaceScopedMetadata, + BaseKubeObjectCondition, +} from "../api-types"; +import { KubeObject } from "../kube-object"; +import type { PodTemplateSpec } from "../types/pod-template-spec"; + +export interface Scale { + apiVersion: "autoscaling/v1"; + kind: "Scale"; + metadata: KubeObjectMetadata; + spec: { + replicas: number; + }; + status: { + replicas: number; + selector: string; + }; +} + +export interface ReplicationControllerSpec { + /** + * Minimum number of seconds for which a newly created pod should be ready without any of its container crashing, + * for it to be considered available. + * Defaults to 0 (pod will be considered available as soon as it is ready) + */ + minReadySeconds?: number; + /** + * Replicas is the number of desired replicas. This is a pointer to distinguish between explicit zero and unspecified. + * Defaults to 1. More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller#what-is-a-replicationcontroller + */ + replicas?: number; + /** + * Selector is a label query over pods that should match the Replicas count. + * If Selector is empty, it is defaulted to the labels present on the Pod template. + * Label keys and values that must match in order to be controlled by this replication controller, + * if empty defaulted to labels on Pod template. + * More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors + */ + selector?: Record; + /** + * Template is the object that describes the pod that will be created if insufficient replicas are detected. + * This takes precedence over a TemplateRef. + * More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller#pod-template + */ + template: PodTemplateSpec; +} + +export interface ReplicationControllerStatus extends KubeObjectStatus { + /** + * The number of available replicas (ready for at least minReadySeconds) for this replication controller. + */ + availableReplicas: number; + /** + * The number of pods that have labels matching the labels of the pod template of the replication controller. + */ + fullyLabeledReplicas: number; + /** + * ObservedGeneration reflects the generation of the most recently observed replication controller. + */ + observedGeneration: number; + /** + * The number of ready replicas for this replication controller. + */ + readyReplicas: number; + /** + * Replicas is the most recently observed number of replicas. + * More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller#what-is-a-replicationcontroller + */ + replicas: number; +} + +export class ReplicationController extends KubeObject< + NamespaceScopedMetadata, + ReplicationControllerStatus, + ReplicationControllerSpec +> { + static kind = "ReplicationController"; + + static namespaced = true; + + static apiBase = "/api/v1/replicationcontrollers"; + + getMinReadySeconds(): number { + return this.spec?.minReadySeconds ?? 0; + } + + getGeneration() { + return this.status?.observedGeneration; + } + + getSelectorLabels(): string[] { + return KubeObject.stringifyLabels(this.spec.selector); + } + + getReplicas(): number | undefined { + return this.status?.replicas; + } + + getDesiredReplicas(): number { + return this.spec?.replicas ?? 0; + } + + getAvailableReplicas(): number | undefined { + return this.status?.availableReplicas; + } + + getLabeledReplicas(): number | undefined { + return this.status?.fullyLabeledReplicas; + } + + getConditions(): BaseKubeObjectCondition[] { + return this.status?.conditions ?? []; + } +} diff --git a/packages/kube-object/src/specifics/resource-quota.ts b/packages/kube-object/src/specifics/resource-quota.ts new file mode 100644 index 0000000000..5b5ce4cf18 --- /dev/null +++ b/packages/kube-object/src/specifics/resource-quota.ts @@ -0,0 +1,61 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import type { NamespaceScopedMetadata } from "../api-types"; +import { KubeObject } from "../kube-object"; + +export type ResourceQuotaValues = Partial> & { + // Compute Resource Quota + "limits.cpu"?: string; + "limits.memory"?: string; + "requests.cpu"?: string; + "requests.memory"?: string; + + // Storage Resource Quota + "requests.storage"?: string; + persistentvolumeclaims?: string; + + // Object Count Quota + "count/pods"?: string; + "count/persistentvolumeclaims"?: string; + "count/services"?: string; + "count/secrets"?: string; + "count/configmaps"?: string; + "count/replicationcontrollers"?: string; + "count/deployments.apps"?: string; + "count/replicasets.apps"?: string; + "count/statefulsets.apps"?: string; + "count/jobs.batch"?: string; + "count/cronjobs.batch"?: string; + "count/deployments.extensions"?: string; +}; + +export interface ResourceQuotaSpec { + hard: ResourceQuotaValues; + scopeSelector?: { + matchExpressions: { + operator: string; + scopeName: string; + values: string[]; + }[]; + }; +} + +export interface ResourceQuotaStatus { + hard: ResourceQuotaValues; + used: ResourceQuotaValues; +} + +export class ResourceQuota extends KubeObject { + static readonly kind = "ResourceQuota"; + + static readonly namespaced = true; + + static readonly apiBase = "/api/v1/resourcequotas"; + + getScopeSelector() { + return this.spec.scopeSelector?.matchExpressions ?? []; + } +} diff --git a/packages/kube-object/src/specifics/role-binding.ts b/packages/kube-object/src/specifics/role-binding.ts new file mode 100644 index 0000000000..a4f04040d7 --- /dev/null +++ b/packages/kube-object/src/specifics/role-binding.ts @@ -0,0 +1,42 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import type { KubeJsonApiData, KubeObjectMetadata, KubeObjectScope, NamespaceScopedMetadata } from "../api-types"; +import { KubeObject } from "../kube-object"; +import type { RoleRef } from "../types/role-ref"; +import type { Subject } from "../types/subject"; + +export interface RoleBindingData extends KubeJsonApiData, void, void> { + subjects?: Subject[]; + roleRef: RoleRef; +} + +export class RoleBinding extends KubeObject { + static readonly kind = "RoleBinding"; + + static readonly namespaced = true; + + static readonly apiBase = "/apis/rbac.authorization.k8s.io/v1/rolebindings"; + + subjects?: Subject[]; + + roleRef: RoleRef; + + constructor({ subjects, roleRef, ...rest }: RoleBindingData) { + super(rest); + this.subjects = subjects; + this.roleRef = roleRef; + } + + getSubjects() { + return this.subjects || []; + } + + getSubjectNames(): string { + return this.getSubjects() + .map((subject) => subject.name) + .join(", "); + } +} diff --git a/packages/kube-object/src/specifics/role.ts b/packages/kube-object/src/specifics/role.ts new file mode 100644 index 0000000000..7b876fa9b7 --- /dev/null +++ b/packages/kube-object/src/specifics/role.ts @@ -0,0 +1,31 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import type { KubeJsonApiData, KubeObjectMetadata, KubeObjectScope, NamespaceScopedMetadata } from "../api-types"; +import { KubeObject } from "../kube-object"; +import type { PolicyRule } from "../types/policy-rule"; + +export interface RoleData extends KubeJsonApiData, void, void> { + rules?: PolicyRule[]; +} + +export class Role extends KubeObject { + static readonly kind = "Role"; + + static readonly namespaced = true; + + static readonly apiBase = "/apis/rbac.authorization.k8s.io/v1/roles"; + + rules?: PolicyRule[]; + + constructor({ rules, ...rest }: RoleData) { + super(rest); + this.rules = rules; + } + + getRules() { + return this.rules || []; + } +} diff --git a/packages/kube-object/src/specifics/runtime-class.ts b/packages/kube-object/src/specifics/runtime-class.ts new file mode 100644 index 0000000000..e09cc0580e --- /dev/null +++ b/packages/kube-object/src/specifics/runtime-class.ts @@ -0,0 +1,65 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import type { + KubeJsonApiData, + KubeObjectMetadata, + KubeObjectScope, + Toleration, + ClusterScopedMetadata, +} from "../api-types"; +import { KubeObject } from "../kube-object"; + +export interface RuntimeClassData extends KubeJsonApiData, void, void> { + handler: string; + overhead?: RuntimeClassOverhead; + scheduling?: RuntimeClassScheduling; +} + +export interface RuntimeClassOverhead { + podFixed?: string; +} + +export interface RuntimeClassScheduling { + nodeSelector?: Partial>; + tolerations?: Toleration[]; +} + +export class RuntimeClass extends KubeObject { + static readonly kind = "RuntimeClass"; + + static readonly namespaced = false; + + static readonly apiBase = "/apis/node.k8s.io/v1/runtimeclasses"; + + handler: string; + + overhead?: RuntimeClassOverhead; + + scheduling?: RuntimeClassScheduling; + + constructor({ handler, overhead, scheduling, ...rest }: RuntimeClassData) { + super(rest); + this.handler = handler; + this.overhead = overhead; + this.scheduling = scheduling; + } + + getHandler() { + return this.handler; + } + + getPodFixed() { + return this.overhead?.podFixed ?? ""; + } + + getNodeSelectors(): string[] { + return Object.entries(this.scheduling?.nodeSelector ?? {}).map((values) => values.join(": ")); + } + + getTolerations() { + return this.scheduling?.tolerations ?? []; + } +} diff --git a/packages/kube-object/src/specifics/secret.ts b/packages/kube-object/src/specifics/secret.ts new file mode 100644 index 0000000000..9d29cf1c61 --- /dev/null +++ b/packages/kube-object/src/specifics/secret.ts @@ -0,0 +1,68 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import autoBind from "auto-bind"; +import type { KubeJsonApiData, KubeObjectMetadata, KubeObjectScope, NamespaceScopedMetadata } from "../api-types"; +import { KubeObject } from "../kube-object"; + +export enum SecretType { + Opaque = "Opaque", + ServiceAccountToken = "kubernetes.io/service-account-token", + Dockercfg = "kubernetes.io/dockercfg", + DockerConfigJson = "kubernetes.io/dockerconfigjson", + BasicAuth = "kubernetes.io/basic-auth", + SSHAuth = "kubernetes.io/ssh-auth", + TLS = "kubernetes.io/tls", + BootstrapToken = "bootstrap.kubernetes.io/token", +} + +export const reverseSecretTypeMap = { + [SecretType.Opaque]: "Opaque", + [SecretType.ServiceAccountToken]: "ServiceAccountToken", + [SecretType.Dockercfg]: "Dockercfg", + [SecretType.DockerConfigJson]: "DockerConfigJson", + [SecretType.BasicAuth]: "BasicAuth", + [SecretType.SSHAuth]: "SSHAuth", + [SecretType.TLS]: "TLS", + [SecretType.BootstrapToken]: "BootstrapToken", +}; + +export interface SecretReference { + name: string; + namespace?: string; +} + +export interface SecretData extends KubeJsonApiData, void, void> { + type: SecretType; + data?: Partial>; +} + +export class Secret extends KubeObject { + static readonly kind = "Secret"; + + static readonly namespaced = true; + + static readonly apiBase = "/api/v1/secrets"; + + type: SecretType; + + data: Partial>; + + constructor({ data = {}, type, ...rest }: SecretData) { + super(rest); + autoBind(this); + + this.data = data; + this.type = type; + } + + getKeys(): string[] { + return Object.keys(this.data); + } + + getToken() { + return this.data.token; + } +} diff --git a/packages/kube-object/src/specifics/self-subject-rules-reviews.ts b/packages/kube-object/src/specifics/self-subject-rules-reviews.ts new file mode 100644 index 0000000000..76d7c9ec5c --- /dev/null +++ b/packages/kube-object/src/specifics/self-subject-rules-reviews.ts @@ -0,0 +1,66 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import type { ClusterScopedMetadata } from "../api-types"; +import { KubeObject } from "../kube-object"; + +export interface SelfSubjectReviewRule { + verbs: string[]; + apiGroups?: string[]; + resources?: string[]; + resourceNames?: string[]; + nonResourceURLs?: string[]; +} + +export interface SelfSubjectRulesReviewSpec { + namespace?: string; +} + +export interface SelfSubjectRulesReviewStatus { + resourceRules: SelfSubjectReviewRule[]; + nonResourceRules: SelfSubjectReviewRule[]; + incomplete: boolean; +} + +export class SelfSubjectRulesReview extends KubeObject< + ClusterScopedMetadata, + SelfSubjectRulesReviewStatus, + SelfSubjectRulesReviewSpec +> { + static kind = "SelfSubjectRulesReview"; + + static namespaced = false; + + static apiBase = "/apis/authorization.k8s.io/v1/selfsubjectrulesreviews"; + + getResourceRules() { + const rules = (this.status && this.status.resourceRules) || []; + + return rules.map((rule) => this.normalize(rule)); + } + + getNonResourceRules() { + const rules = (this.status && this.status.nonResourceRules) || []; + + return rules.map((rule) => this.normalize(rule)); + } + + protected normalize(rule: SelfSubjectReviewRule): SelfSubjectReviewRule { + const { apiGroups = [], resourceNames = [], verbs = [], nonResourceURLs = [], resources = [] } = rule; + + return { + apiGroups, + nonResourceURLs, + resourceNames, + verbs, + resources: resources.map((resource, index) => { + const apiGroup = apiGroups.length >= index + 1 ? apiGroups[index] : apiGroups.slice(-1)[0]; + const separator = apiGroup === "" ? "" : "."; + + return resource + separator + apiGroup; + }), + }; + } +} diff --git a/packages/kube-object/src/specifics/service-account.ts b/packages/kube-object/src/specifics/service-account.ts new file mode 100644 index 0000000000..834206a9c4 --- /dev/null +++ b/packages/kube-object/src/specifics/service-account.ts @@ -0,0 +1,49 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import type { + KubeJsonApiData, + KubeObjectMetadata, + KubeObjectScope, + LocalObjectReference, + ObjectReference, + NamespaceScopedMetadata, +} from "../api-types"; +import { KubeObject } from "../kube-object"; + +export interface ServiceAccountData extends KubeJsonApiData, void, void> { + automountServiceAccountToken?: boolean; + imagePullSecrets?: LocalObjectReference[]; + secrets?: ObjectReference[]; +} + +export class ServiceAccount extends KubeObject { + static readonly kind = "ServiceAccount"; + + static readonly namespaced = true; + + static readonly apiBase = "/api/v1/serviceaccounts"; + + automountServiceAccountToken?: boolean; + + imagePullSecrets?: LocalObjectReference[]; + + secrets?: ObjectReference[]; + + constructor({ automountServiceAccountToken, imagePullSecrets, secrets, ...rest }: ServiceAccountData) { + super(rest); + this.automountServiceAccountToken = automountServiceAccountToken; + this.imagePullSecrets = imagePullSecrets; + this.secrets = secrets; + } + + getSecrets() { + return this.secrets || []; + } + + getImagePullSecrets() { + return this.imagePullSecrets || []; + } +} diff --git a/packages/kube-object/src/specifics/service.ts b/packages/kube-object/src/specifics/service.ts new file mode 100644 index 0000000000..8fcdc55c08 --- /dev/null +++ b/packages/kube-object/src/specifics/service.ts @@ -0,0 +1,139 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import type { NamespaceScopedMetadata } from "../api-types"; +import { KubeObject } from "../kube-object"; + +export interface ServicePortSpec { + name?: string; + protocol: string; + port: number; + targetPort: number; + nodePort?: number; +} + +export class ServicePort { + name?: string; + + protocol: string; + + port: number; + + targetPort: number; + + nodePort?: number; + + constructor(data: ServicePortSpec) { + this.name = data.name; + this.protocol = data.protocol; + this.port = data.port; + this.targetPort = data.targetPort; + this.nodePort = data.nodePort; + } + + toString() { + const targetPort = this.nodePort ? `:${this.nodePort}` : this.port !== this.targetPort ? `:${this.targetPort}` : ""; + + return `${this.port}${targetPort}/${this.protocol}`; + } +} + +export interface ServiceSpec { + type: string; + clusterIP: string; + clusterIPs?: string[]; + externalTrafficPolicy?: string; + externalName?: string; + loadBalancerIP?: string; + loadBalancerSourceRanges?: string[]; + sessionAffinity: string; + selector: Partial>; + ports: ServicePortSpec[]; + healthCheckNodePort?: number; + externalIPs?: string[]; // https://kubernetes.io/docs/concepts/services-networking/service/#external-ips + topologyKeys?: string[]; + ipFamilies?: string[]; + ipFamilyPolicy?: string; + allocateLoadBalancerNodePorts?: boolean; + loadBalancerClass?: string; + internalTrafficPolicy?: string; +} + +export interface ServiceStatus { + loadBalancer?: { + ingress?: { + ip?: string; + hostname?: string; + }[]; + }; +} + +export class Service extends KubeObject { + static readonly kind = "Service"; + + static readonly namespaced = true; + + static readonly apiBase = "/api/v1/services"; + + getClusterIp() { + return this.spec.clusterIP; + } + + getClusterIps() { + return this.spec.clusterIPs || []; + } + + getExternalIps() { + const lb = this.getLoadBalancer(); + + if (lb?.ingress) { + return lb.ingress.map((val) => val.ip || val.hostname); + } + + if (Array.isArray(this.spec?.externalIPs)) { + return this.spec.externalIPs; + } + + return []; + } + + getType() { + return this.spec.type || "-"; + } + + getSelector(): string[] { + if (!this.spec.selector) { + return []; + } + + return Object.entries(this.spec.selector).map((val) => val.join("=")); + } + + getPorts(): ServicePort[] { + const ports = this.spec.ports || []; + + return ports.map((p) => new ServicePort(p)); + } + + getLoadBalancer() { + return this.status?.loadBalancer; + } + + isActive() { + return this.getType() !== "LoadBalancer" || this.getExternalIps().length > 0; + } + + getStatus() { + return this.isActive() ? "Active" : "Pending"; + } + + getIpFamilies() { + return this.spec.ipFamilies || []; + } + + getIpFamilyPolicy() { + return this.spec.ipFamilyPolicy || ""; + } +} diff --git a/packages/kube-object/src/specifics/stateful-set.ts b/packages/kube-object/src/specifics/stateful-set.ts new file mode 100644 index 0000000000..44f1f38886 --- /dev/null +++ b/packages/kube-object/src/specifics/stateful-set.ts @@ -0,0 +1,69 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import type { LabelSelector, NamespaceScopedMetadata } from "../api-types"; +import { KubeObject } from "../kube-object"; +import type { PersistentVolumeClaimTemplateSpec } from "../types/persistent-volume-claim-template-spec"; +import type { PodTemplateSpec } from "../types/pod-template-spec"; + +export interface StatefulSetSpec { + serviceName: string; + replicas: number; + selector: LabelSelector; + template: PodTemplateSpec; + volumeClaimTemplates: PersistentVolumeClaimTemplateSpec[]; +} + +export interface StatefulSetStatus { + observedGeneration: number; + replicas: number; + currentReplicas: number; + readyReplicas: number; + currentRevision: string; + updateRevision: string; + collisionCount: number; +} + +export class StatefulSet extends KubeObject { + static readonly kind = "StatefulSet"; + + static readonly namespaced = true; + + static readonly apiBase = "/apis/apps/v1/statefulsets"; + + getSelectors(): string[] { + return KubeObject.stringifyLabels(this.spec.selector.matchLabels); + } + + getNodeSelectors(): string[] { + return KubeObject.stringifyLabels(this.spec.template.spec?.nodeSelector); + } + + getTemplateLabels(): string[] { + return KubeObject.stringifyLabels(this.spec.template.metadata?.labels); + } + + getTolerations() { + return this.spec.template.spec?.tolerations ?? []; + } + + getAffinity() { + return this.spec.template.spec?.affinity ?? {}; + } + + getAffinityNumber() { + return Object.keys(this.getAffinity()).length; + } + + getReplicas() { + return this.spec.replicas || 0; + } + + getImages() { + const containers = this.spec.template?.spec?.containers ?? []; + + return containers.map((container) => container.image); + } +} diff --git a/packages/kube-object/src/specifics/storage-class.ts b/packages/kube-object/src/specifics/storage-class.ts new file mode 100644 index 0000000000..9978b99537 --- /dev/null +++ b/packages/kube-object/src/specifics/storage-class.ts @@ -0,0 +1,87 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import autoBind from "auto-bind"; +import type { KubeJsonApiData, KubeObjectMetadata, KubeObjectScope, ClusterScopedMetadata } from "../api-types"; +import { KubeObject } from "../kube-object"; + +export interface TopologySelectorLabelRequirement { + key: string; + values: string[]; +} + +export interface TopologySelectorTerm { + matchLabelExpressions?: TopologySelectorLabelRequirement[]; +} + +export interface StorageClassData extends KubeJsonApiData, void, void> { + allowVolumeExpansion?: boolean; + allowedTopologies?: TopologySelectorTerm[]; + mountOptions?: string[]; + parameters?: Partial>; + provisioner: string; + reclaimPolicy?: string; + volumeBindingMode?: string; +} + +export class StorageClass extends KubeObject { + static readonly kind = "StorageClass"; + + static readonly namespaced = false; + + static readonly apiBase = "/apis/storage.k8s.io/v1/storageclasses"; + + allowVolumeExpansion?: boolean; + + allowedTopologies: TopologySelectorTerm[]; + + mountOptions: string[]; + + parameters: Partial>; + + provisioner: string; + + reclaimPolicy: string; + + volumeBindingMode?: string; + + constructor({ + allowVolumeExpansion, + allowedTopologies = [], + mountOptions = [], + parameters = {}, + provisioner, + reclaimPolicy = "Delete", + volumeBindingMode, + ...rest + }: StorageClassData) { + super(rest); + autoBind(this); + this.allowVolumeExpansion = allowVolumeExpansion; + this.allowedTopologies = allowedTopologies; + this.mountOptions = mountOptions; + this.parameters = parameters; + this.provisioner = provisioner; + this.reclaimPolicy = reclaimPolicy; + this.volumeBindingMode = volumeBindingMode; + } + + isDefault() { + const annotations = this.metadata.annotations || {}; + + return ( + annotations["storageclass.kubernetes.io/is-default-class"] === "true" || + annotations["storageclass.beta.kubernetes.io/is-default-class"] === "true" + ); + } + + getVolumeBindingMode() { + return this.volumeBindingMode || "-"; + } + + getReclaimPolicy() { + return this.reclaimPolicy || "-"; + } +} diff --git a/packages/kube-object/src/specifics/validating-webhook-configuration.ts b/packages/kube-object/src/specifics/validating-webhook-configuration.ts new file mode 100644 index 0000000000..befc068b55 --- /dev/null +++ b/packages/kube-object/src/specifics/validating-webhook-configuration.ts @@ -0,0 +1,32 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import type { KubeJsonApiData, KubeObjectMetadata, KubeObjectScope, NamespaceScopedMetadata } from "../api-types"; +import { KubeObject } from "../kube-object"; +import type { Webhook } from "./mutating-webhook-configuration"; + +export interface ValidatingWebhookConfigurationData + extends KubeJsonApiData, void, void> { + webhooks?: Webhook[]; +} + +export class ValidatingWebhookConfiguration extends KubeObject { + static kind = "ValidatingWebhookConfiguration"; + + static namespaced = false; + + static apiBase = "/apis/admissionregistration.k8s.io/v1/validatingwebhookconfigurations"; + + webhooks?: Webhook[]; + + constructor({ webhooks, ...rest }: ValidatingWebhookConfigurationData) { + super(rest); + this.webhooks = webhooks; + } + + getWebhooks(): Webhook[] { + return this.webhooks ?? []; + } +} diff --git a/packages/kube-object/src/specifics/vertical-pod-autoscaler.ts b/packages/kube-object/src/specifics/vertical-pod-autoscaler.ts new file mode 100644 index 0000000000..25ef2f74b8 --- /dev/null +++ b/packages/kube-object/src/specifics/vertical-pod-autoscaler.ts @@ -0,0 +1,142 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import type { BaseKubeObjectCondition, NamespaceScopedMetadata } from "../api-types"; +import { KubeObject } from "../kube-object"; +import type { CrossVersionObjectReference } from "../types/cross-version-object-reference"; + +export enum ResourceName { + ResourceCPU = "cpu", + ResourceMemory = "memory", + ResourceStorage = "storage", +} + +export type ResourceList = Partial>; + +export interface RecommendedContainerResources { + containerName?: string; + target: ResourceList; + lowerBound?: ResourceList; + upperBound?: ResourceList; + uncappedTarget?: ResourceList; +} +export interface RecommendedPodResources { + containerRecommendations?: RecommendedContainerResources[]; +} + +export interface VerticalPodAutoscalerStatus { + conditions?: BaseKubeObjectCondition[]; + recommendation?: RecommendedPodResources; +} + +export interface VerticalPodAutoscalerRecommenderSelector { + name: string; +} + +export enum ContainerScalingMode { + ContainerScalingModeAuto = "Auto", + ContainerScalingModeOff = "Off", +} + +export enum ControlledValues { + ControlledValueRequestsAndLimits = "RequestsAndLimits", + ControlledValueRequestsOnly = "RequestsOnly", +} + +/** + * ContainerResourcePolicy controls how autoscaler computes the recommended resources for + * a specific container. + */ +export interface ContainerResourcePolicy { + containerName?: string; + mode?: ContainerScalingMode; + minAllowed?: ResourceList; + maxAllowed?: ResourceList; + controlledResources?: ResourceName[]; + controlledValues?: ControlledValues; +} + +/** + * Controls how the autoscaler computes recommended resources. + * The resource policy may be used to set constraints on the recommendations for individual + * containers. + * If not specified, the autoscaler computes recommended resources for all containers in the + * pod, without additional constraints. + */ +export interface PodResourcePolicy { + containerPolicies?: ContainerResourcePolicy[]; // Per-container resource policies. +} + +export enum UpdateMode { + /** + * UpdateModeOff means that autoscaler never changes Pod resources. + * The recommender still sets the recommended resources in the + * VerticalPodAutoscaler object. This can be used for a "dry run". + */ + UpdateModeOff = "Off", + /** + * UpdateModeInitial means that autoscaler only assigns resources on pod + * creation and does not change them during the lifetime of the pod. + */ + UpdateModeInitial = "Initial", + /** + * UpdateModeRecreate means that autoscaler assigns resources on pod + * creation and additionally can update them during the lifetime of the + * pod by deleting and recreating the pod. + */ + UpdateModeRecreate = "Recreate", + /** + * UpdateModeAuto means that autoscaler assigns resources on pod creation + * and additionally can update them during the lifetime of the pod, + * using any available update method. Currently this is equivalent to + * Recreate, which is the only available update method. + */ + UpdateModeAuto = "Auto", +} +export interface PodUpdatePolicy { + minReplicas?: number; + updateMode?: UpdateMode; +} + +export interface VerticalPodAutoscalerSpec { + targetRef: CrossVersionObjectReference; + updatePolicy?: PodUpdatePolicy; + resourcePolicy?: PodResourcePolicy; + recommenders?: VerticalPodAutoscalerRecommenderSelector[]; +} + +export class VerticalPodAutoscaler extends KubeObject< + NamespaceScopedMetadata, + VerticalPodAutoscalerStatus, + VerticalPodAutoscalerSpec +> { + static readonly kind = "VerticalPodAutoscaler"; + + static readonly namespaced = true; + + static readonly apiBase = "/apis/autoscaling.k8s.io/v1/verticalpodautoscalers"; + + getReadyConditions() { + return this.getConditions().filter(({ isReady }) => isReady); + } + + getConditions() { + return ( + this.status?.conditions?.map((condition) => { + const { message, reason, lastTransitionTime, status } = condition; + + return { + ...condition, + isReady: status === "True", + tooltip: `${message || reason || ""} (${lastTransitionTime})`, + }; + }) ?? [] + ); + } + + getMode() { + return this.spec.updatePolicy?.updateMode ?? UpdateMode.UpdateModeAuto; + } +} diff --git a/packages/core/src/common/k8s-api/endpoints/types/aggregation-rule.ts b/packages/kube-object/src/types/aggregation-rule.ts similarity index 80% rename from packages/core/src/common/k8s-api/endpoints/types/aggregation-rule.ts rename to packages/kube-object/src/types/aggregation-rule.ts index 57ebe32264..6c60c955ec 100644 --- a/packages/core/src/common/k8s-api/endpoints/types/aggregation-rule.ts +++ b/packages/kube-object/src/types/aggregation-rule.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import type { LabelSelector } from "../../kube-object"; +import type { LabelSelector } from "../api-types"; export interface AggregationRule { clusterRoleSelectors?: LabelSelector; diff --git a/packages/core/src/common/k8s-api/endpoints/types/capabilities.ts b/packages/kube-object/src/types/capabilities.ts similarity index 100% rename from packages/core/src/common/k8s-api/endpoints/types/capabilities.ts rename to packages/kube-object/src/types/capabilities.ts diff --git a/packages/core/src/common/k8s-api/endpoints/types/condition.ts b/packages/kube-object/src/types/condition.ts similarity index 100% rename from packages/core/src/common/k8s-api/endpoints/types/condition.ts rename to packages/kube-object/src/types/condition.ts diff --git a/packages/core/src/common/k8s-api/endpoints/types/container-port.ts b/packages/kube-object/src/types/container-port.ts similarity index 100% rename from packages/core/src/common/k8s-api/endpoints/types/container-port.ts rename to packages/kube-object/src/types/container-port.ts diff --git a/packages/core/src/common/k8s-api/endpoints/types/container.ts b/packages/kube-object/src/types/container.ts similarity index 100% rename from packages/core/src/common/k8s-api/endpoints/types/container.ts rename to packages/kube-object/src/types/container.ts diff --git a/packages/core/src/common/k8s-api/endpoints/types/cross-version-object-reference.ts b/packages/kube-object/src/types/cross-version-object-reference.ts similarity index 99% rename from packages/core/src/common/k8s-api/endpoints/types/cross-version-object-reference.ts rename to packages/kube-object/src/types/cross-version-object-reference.ts index b75369bb9b..2dd82b8616 100644 --- a/packages/core/src/common/k8s-api/endpoints/types/cross-version-object-reference.ts +++ b/packages/kube-object/src/types/cross-version-object-reference.ts @@ -8,4 +8,3 @@ export interface CrossVersionObjectReference { name: string; apiVersion: string; } - diff --git a/packages/core/src/common/k8s-api/endpoints/types/env-from-source.ts b/packages/kube-object/src/types/env-from-source.ts similarity index 100% rename from packages/core/src/common/k8s-api/endpoints/types/env-from-source.ts rename to packages/kube-object/src/types/env-from-source.ts diff --git a/packages/core/src/common/k8s-api/endpoints/types/env-source.ts b/packages/kube-object/src/types/env-source.ts similarity index 81% rename from packages/core/src/common/k8s-api/endpoints/types/env-source.ts rename to packages/kube-object/src/types/env-source.ts index 2a16ee2ada..e7d8204971 100644 --- a/packages/core/src/common/k8s-api/endpoints/types/env-source.ts +++ b/packages/kube-object/src/types/env-source.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import type { LocalObjectReference } from "../../kube-object"; +import type { LocalObjectReference } from "../api-types"; export interface EnvSource extends LocalObjectReference { /** diff --git a/packages/core/src/common/k8s-api/endpoints/types/env-var-key-selector.ts b/packages/kube-object/src/types/env-var-key-selector.ts similarity index 100% rename from packages/core/src/common/k8s-api/endpoints/types/env-var-key-selector.ts rename to packages/kube-object/src/types/env-var-key-selector.ts diff --git a/packages/core/src/common/k8s-api/endpoints/types/env-var-source.ts b/packages/kube-object/src/types/env-var-source.ts similarity index 100% rename from packages/core/src/common/k8s-api/endpoints/types/env-var-source.ts rename to packages/kube-object/src/types/env-var-source.ts diff --git a/packages/core/src/common/k8s-api/endpoints/types/env-var.ts b/packages/kube-object/src/types/env-var.ts similarity index 100% rename from packages/core/src/common/k8s-api/endpoints/types/env-var.ts rename to packages/kube-object/src/types/env-var.ts diff --git a/packages/core/src/common/k8s-api/endpoints/types/exec-action.ts b/packages/kube-object/src/types/exec-action.ts similarity index 100% rename from packages/core/src/common/k8s-api/endpoints/types/exec-action.ts rename to packages/kube-object/src/types/exec-action.ts diff --git a/packages/core/src/common/k8s-api/endpoints/types/external-documentation.ts b/packages/kube-object/src/types/external-documentation.ts similarity index 100% rename from packages/core/src/common/k8s-api/endpoints/types/external-documentation.ts rename to packages/kube-object/src/types/external-documentation.ts diff --git a/packages/core/src/common/k8s-api/endpoints/types/handler.ts b/packages/kube-object/src/types/handler.ts similarity index 100% rename from packages/core/src/common/k8s-api/endpoints/types/handler.ts rename to packages/kube-object/src/types/handler.ts diff --git a/packages/core/src/common/k8s-api/endpoints/types/http-get-action.ts b/packages/kube-object/src/types/http-get-action.ts similarity index 100% rename from packages/core/src/common/k8s-api/endpoints/types/http-get-action.ts rename to packages/kube-object/src/types/http-get-action.ts diff --git a/packages/core/src/common/k8s-api/endpoints/types/http-header.ts b/packages/kube-object/src/types/http-header.ts similarity index 100% rename from packages/core/src/common/k8s-api/endpoints/types/http-header.ts rename to packages/kube-object/src/types/http-header.ts diff --git a/packages/core/src/common/k8s-api/endpoints/types/index.ts b/packages/kube-object/src/types/index.ts similarity index 100% rename from packages/core/src/common/k8s-api/endpoints/types/index.ts rename to packages/kube-object/src/types/index.ts diff --git a/packages/core/src/common/k8s-api/endpoints/types/job-template-spec.ts b/packages/kube-object/src/types/job-template-spec.ts similarity index 83% rename from packages/core/src/common/k8s-api/endpoints/types/job-template-spec.ts rename to packages/kube-object/src/types/job-template-spec.ts index b3ae5cceca..2dd027177b 100644 --- a/packages/core/src/common/k8s-api/endpoints/types/job-template-spec.ts +++ b/packages/kube-object/src/types/job-template-spec.ts @@ -3,8 +3,8 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import type { KubeObjectScope, KubeTemplateObjectMetadata } from "../../kube-object"; -import type { JobSpec } from "../job.api"; +import type { KubeObjectScope, KubeTemplateObjectMetadata } from "../api-types"; +import type { JobSpec } from "../specifics/job"; export interface JobTemplateSpec { metadata?: KubeTemplateObjectMetadata; diff --git a/packages/core/src/common/k8s-api/endpoints/types/json-schema-props.ts b/packages/kube-object/src/types/json-schema-props.ts similarity index 78% rename from packages/core/src/common/k8s-api/endpoints/types/json-schema-props.ts rename to packages/kube-object/src/types/json-schema-props.ts index 1c0f18a7d2..3225601585 100644 --- a/packages/core/src/common/k8s-api/endpoints/types/json-schema-props.ts +++ b/packages/kube-object/src/types/json-schema-props.ts @@ -6,6 +6,33 @@ import type { JsonValue } from "type-fest"; import type { ExternalDocumentation } from "./external-documentation"; +/** + * (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{12}$ + */ +export type UUIDRegexString = string; + +/** + * (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?3[0-9a-f]{3}-?[0-9a-f]{4}-?[0-9a-f]{12}$ + */ +export type UUID3RegexString = string; + +/** + * (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?4[0-9a-f]{3}-?[89ab][0-9a-f]{3}-?[0-9a-f]{12}$ + */ +export type UUID4RegexString = string; + +/** + * (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?5[0-9a-f]{3}-?[89ab][0-9a-f]{3}-?[0-9a-f]{12}$ + */ +export type UUID5RegexString = string; + +/* eslint-disable max-len */ +/** + * ^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\\d{3})\\d{11})$ + */ +export type CreditCardRegexString = string; +/* eslint-enable max-len */ + export interface JSONSchemaProps { $ref?: string; $schema?: string; @@ -44,21 +71,21 @@ export interface JSONSchemaProps { * - ipv6: an IPv6 IP as parsed by Golang net.ParseIP * - cidr: a CIDR as parsed by Golang net.ParseCIDR * - mac: a MAC address as parsed by Golang net.ParseMAC - * - uuid: an UUID that allows uppercase defined by the regex (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{12}$ - * - uuid3: an UUID3 that allows uppercase defined by the regex (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?3[0-9a-f]{3}-?[0-9a-f]{4}-?[0-9a-f]{12}$ - * - uuid4: an UUID4 that allows uppercase defined by the regex (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?4[0-9a-f]{3}-?[89ab][0-9a-f]{3}-?[0-9a-f]{12}$ - * - uuid5: an UUID5 that allows uppercase defined by the regex (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?5[0-9a-f]{3}-?[89ab][0-9a-f]{3}-?[0-9a-f]{12}$ + * - uuid: an UUID that allows uppercase defined by the regex {@link UUIDRegexString} + * - uuid3: an UUID3 that allows uppercase defined by the regex {@link UUID3RegexString} + * - uuid4: an UUID4 that allows uppercase defined by the regex {@link UUID4RegexString} + * - uuid5: an UUID5 that allows uppercase defined by the regex {@link UUID5RegexString} * - isbn: an ISBN10 or ISBN13 number string like "0321751043" or "978-0321751041" * - isbn10: an ISBN10 number string like "0321751043" * - isbn13: an ISBN13 number string like "978-0321751041" - * - creditcard: a credit card number defined by the regex ^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\\d{3})\\d{11})$ with any non digit characters mixed in + * - creditcard: a credit card number defined by the regex {@link CreditCardRegexString} * - ssn: a U.S. social security number following the regex ^\\d{3}[- ]?\\d{2}[- ]?\\d{4}$ * - hexcolor: an hexadecimal color code like "#FFFFFF: following the regex ^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$ * - rgbcolor: an RGB color code like rgb like "rgb(255,255,2559" * - byte: base64 encoded binary data * - password: any kind of string * - date: a date string like "2006-01-02" as defined by full-date in RFC3339 - * - duration: a duration string like "22 ns" as parsed by Golang time.ParseDuration or compatible with Scala duration format + * - duration: a duration string like "22 ns" as parsed by Golang time.ParseDuration or with Scala's duration format * - datetime: a date time string like "2014-12-15T19:30:20.000Z" as defined by date-time in RFC3339. */ format?: string; diff --git a/packages/core/src/common/k8s-api/endpoints/types/lifecycle.ts b/packages/kube-object/src/types/lifecycle.ts similarity index 100% rename from packages/core/src/common/k8s-api/endpoints/types/lifecycle.ts rename to packages/kube-object/src/types/lifecycle.ts diff --git a/packages/core/src/common/k8s-api/endpoints/types/object-field-selector.ts b/packages/kube-object/src/types/object-field-selector.ts similarity index 100% rename from packages/core/src/common/k8s-api/endpoints/types/object-field-selector.ts rename to packages/kube-object/src/types/object-field-selector.ts diff --git a/packages/core/src/common/k8s-api/endpoints/types/persistent-volume-claim-template-spec.ts b/packages/kube-object/src/types/persistent-volume-claim-template-spec.ts similarity index 79% rename from packages/core/src/common/k8s-api/endpoints/types/persistent-volume-claim-template-spec.ts rename to packages/kube-object/src/types/persistent-volume-claim-template-spec.ts index 5461c81807..a5b63f6bf5 100644 --- a/packages/core/src/common/k8s-api/endpoints/types/persistent-volume-claim-template-spec.ts +++ b/packages/kube-object/src/types/persistent-volume-claim-template-spec.ts @@ -3,8 +3,8 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import type { KubeObjectScope, KubeTemplateObjectMetadata } from "../../kube-object"; -import type { PersistentVolumeSpec } from "../persistent-volume.api"; +import type { KubeObjectScope, KubeTemplateObjectMetadata } from "../api-types"; +import type { PersistentVolumeSpec } from "../specifics/persistent-volume"; export interface PersistentVolumeClaimTemplateSpec { metadata?: KubeTemplateObjectMetadata; diff --git a/packages/core/src/common/k8s-api/endpoints/types/pod-security-context.ts b/packages/kube-object/src/types/pod-security-context.ts similarity index 93% rename from packages/core/src/common/k8s-api/endpoints/types/pod-security-context.ts rename to packages/kube-object/src/types/pod-security-context.ts index 983ae86262..8bb0432796 100644 --- a/packages/core/src/common/k8s-api/endpoints/types/pod-security-context.ts +++ b/packages/kube-object/src/types/pod-security-context.ts @@ -5,8 +5,7 @@ import type { SeLinuxOptions } from "./se-linux-options"; import type { SeccompProfile } from "./seccomp-profile"; import type { WindowsSecurityContextOptions } from "./windows-security-context-options"; -import type { Sysctl } from "../pod.api"; - +import type { Sysctl } from "../specifics/pod"; export interface PodSecurityContext { fsGroup?: number; diff --git a/packages/core/src/common/k8s-api/endpoints/types/pod-template-spec.ts b/packages/kube-object/src/types/pod-template-spec.ts similarity index 83% rename from packages/core/src/common/k8s-api/endpoints/types/pod-template-spec.ts rename to packages/kube-object/src/types/pod-template-spec.ts index ba0df4aec7..fb426588d6 100644 --- a/packages/core/src/common/k8s-api/endpoints/types/pod-template-spec.ts +++ b/packages/kube-object/src/types/pod-template-spec.ts @@ -3,8 +3,8 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import type { KubeObjectScope, KubeTemplateObjectMetadata } from "../../kube-object"; -import type { PodSpec } from "../pod.api"; +import type { KubeObjectScope, KubeTemplateObjectMetadata } from "../api-types"; +import type { PodSpec } from "../specifics/pod"; export interface PodTemplateSpec { metadata?: KubeTemplateObjectMetadata; diff --git a/packages/core/src/common/k8s-api/endpoints/types/policy-rule.ts b/packages/kube-object/src/types/policy-rule.ts similarity index 100% rename from packages/core/src/common/k8s-api/endpoints/types/policy-rule.ts rename to packages/kube-object/src/types/policy-rule.ts diff --git a/packages/core/src/common/k8s-api/endpoints/types/preemption-policy.ts b/packages/kube-object/src/types/preemption-policy.ts similarity index 100% rename from packages/core/src/common/k8s-api/endpoints/types/preemption-policy.ts rename to packages/kube-object/src/types/preemption-policy.ts diff --git a/packages/core/src/common/k8s-api/endpoints/types/probe.ts b/packages/kube-object/src/types/probe.ts similarity index 97% rename from packages/core/src/common/k8s-api/endpoints/types/probe.ts rename to packages/kube-object/src/types/probe.ts index 4251be5e2c..249b9e1bf5 100644 --- a/packages/core/src/common/k8s-api/endpoints/types/probe.ts +++ b/packages/kube-object/src/types/probe.ts @@ -8,7 +8,8 @@ import type { HttpGetAction } from "./http-get-action"; import type { TcpSocketAction } from "./tcp-socket-action"; /** - * Describes a health check to be performed against a container to determine whether it is alive or ready to receive traffic. + * Describes a health check to be performed against a container to determine whether + * it is alive or ready to receive traffic. */ export interface Probe { exec?: ExecAction; diff --git a/packages/core/src/common/k8s-api/endpoints/types/resource-field-selector.ts b/packages/kube-object/src/types/resource-field-selector.ts similarity index 100% rename from packages/core/src/common/k8s-api/endpoints/types/resource-field-selector.ts rename to packages/kube-object/src/types/resource-field-selector.ts diff --git a/packages/core/src/common/k8s-api/endpoints/types/resource-requirements.ts b/packages/kube-object/src/types/resource-requirements.ts similarity index 79% rename from packages/core/src/common/k8s-api/endpoints/types/resource-requirements.ts rename to packages/kube-object/src/types/resource-requirements.ts index eb3b6caffa..7d4155f226 100644 --- a/packages/core/src/common/k8s-api/endpoints/types/resource-requirements.ts +++ b/packages/kube-object/src/types/resource-requirements.ts @@ -8,10 +8,10 @@ */ export interface ResourceRequirements { /** - * Limits describes the maximum amount of compute resources allowed. - * - * More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ - */ + * Limits describes the maximum amount of compute resources allowed. + * + * More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + */ limits?: Partial>; /** diff --git a/packages/core/src/common/k8s-api/endpoints/types/role-ref.ts b/packages/kube-object/src/types/role-ref.ts similarity index 100% rename from packages/core/src/common/k8s-api/endpoints/types/role-ref.ts rename to packages/kube-object/src/types/role-ref.ts diff --git a/packages/core/src/common/k8s-api/endpoints/types/se-linux-options.ts b/packages/kube-object/src/types/se-linux-options.ts similarity index 100% rename from packages/core/src/common/k8s-api/endpoints/types/se-linux-options.ts rename to packages/kube-object/src/types/se-linux-options.ts diff --git a/packages/core/src/common/k8s-api/endpoints/types/seccomp-profile.ts b/packages/kube-object/src/types/seccomp-profile.ts similarity index 100% rename from packages/core/src/common/k8s-api/endpoints/types/seccomp-profile.ts rename to packages/kube-object/src/types/seccomp-profile.ts diff --git a/packages/kube-object/src/types/security-context.ts b/packages/kube-object/src/types/security-context.ts new file mode 100644 index 0000000000..761f401ccb --- /dev/null +++ b/packages/kube-object/src/types/security-context.ts @@ -0,0 +1,78 @@ +/** + * Copyright (c) OpenLens Authors. + * All rights reserved. + * Licensed under MIT License. + * See LICENSE in root directory for more information. + */ + +import type { Capabilities } from "./capabilities"; +import type { SeLinuxOptions } from "./se-linux-options"; +import type { SeccompProfile } from "./seccomp-profile"; +import type { WindowsSecurityContextOptions } from "./windows-security-context-options"; + +/** + * SecurityContext holds security configuration that will be applied to a container. + * Some fields are present in both SecurityContext and PodSecurityContext. + * When both are set, the values in SecurityContext take precedence. + */ +export interface SecurityContext { + /** + * AllowPrivilegeEscalation controls whether a process can gain more privileges than its parent process. + * This bool directly controls if the no_new_privs flag will be set on the container process. + * AllowPrivilegeEscalation is true always when the container is: 1) run as Privileged 2) has CAP_SYS_ADMIN + */ + allowPrivilegeEscalation?: boolean; + + capabilities?: Capabilities; + + /** + * Run container in privileged mode. + * Processes in privileged containers are essentially equivalent to root on the host. + * + * @default false + */ + privileged?: boolean; + + /** + * procMount denotes the type of proc mount to use for the containers. + * The default is DefaultProcMount which uses the container runtime defaults for readonly paths and masked paths. + * This requires the ProcMountType feature flag to be enabled. + */ + procMount?: string; + + /** + * Whether this container has a read-only root filesystem. + * @default false + */ + readOnlyRootFilesystem?: boolean; + + /** + * The GID to run the entrypoint of the container process. + * Uses runtime default if unset. + * May also be set in PodSecurityContext. + * If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. + */ + runAsGroup?: number; + + /** + * Indicates that the container must run as a non-root user. + * If true, the Kubelet will validate the image at runtime to ensure that it does not run as UID 0 (root) + * and fail to start the container if it does. + * If unset or false, no such validation will be performed. + * May also be set in PodSecurityContext. + * If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. + */ + runAsNonRoot?: boolean; + + /** + * The UID to run the entrypoint of the container process. + * Defaults to user specified in image metadata if unspecified. + * May also be set in PodSecurityContext. + * If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. + */ + runAsUser?: number; + + seLinuxOptions?: SeLinuxOptions; + seccompProfile?: SeccompProfile; + windowsOptions?: WindowsSecurityContextOptions; +} diff --git a/packages/core/src/common/k8s-api/endpoints/types/subject.ts b/packages/kube-object/src/types/subject.ts similarity index 100% rename from packages/core/src/common/k8s-api/endpoints/types/subject.ts rename to packages/kube-object/src/types/subject.ts diff --git a/packages/core/src/common/k8s-api/endpoints/types/tcp-socket-action.ts b/packages/kube-object/src/types/tcp-socket-action.ts similarity index 100% rename from packages/core/src/common/k8s-api/endpoints/types/tcp-socket-action.ts rename to packages/kube-object/src/types/tcp-socket-action.ts diff --git a/packages/core/src/common/k8s-api/endpoints/types/volume-device.ts b/packages/kube-object/src/types/volume-device.ts similarity index 100% rename from packages/core/src/common/k8s-api/endpoints/types/volume-device.ts rename to packages/kube-object/src/types/volume-device.ts diff --git a/packages/core/src/common/k8s-api/endpoints/types/volume-mount.ts b/packages/kube-object/src/types/volume-mount.ts similarity index 100% rename from packages/core/src/common/k8s-api/endpoints/types/volume-mount.ts rename to packages/kube-object/src/types/volume-mount.ts diff --git a/packages/core/src/common/k8s-api/endpoints/types/windows-security-context-options.ts b/packages/kube-object/src/types/windows-security-context-options.ts similarity index 84% rename from packages/core/src/common/k8s-api/endpoints/types/windows-security-context-options.ts rename to packages/kube-object/src/types/windows-security-context-options.ts index 07daf48731..00f74a8c23 100644 --- a/packages/core/src/common/k8s-api/endpoints/types/windows-security-context-options.ts +++ b/packages/kube-object/src/types/windows-security-context-options.ts @@ -34,7 +34,10 @@ export interface WindowsSecurityContextOptions { hostProcess?: boolean; /** - * The UserName in Windows to run the entrypoint of the container process. Defaults to the user specified in image metadata if unspecified. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. + * The UserName in Windows to run the entrypoint of the container process. + * Defaults to the user specified in image metadata if unspecified. + * May also be set in PodSecurityContext. + * If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. */ runAsUserName?: string; } diff --git a/packages/kube-object/src/utils.ts b/packages/kube-object/src/utils.ts new file mode 100644 index 0000000000..d740dd1975 --- /dev/null +++ b/packages/kube-object/src/utils.ts @@ -0,0 +1,113 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import { + isObject, + hasTypedProperty, + isString, + hasOptionalTypedProperty, + bindPredicate, + isTypedArray, + isRecord, +} from "@k8slens/utilities"; +import type { + KubeJsonApiData, + KubeObjectMetadata, + KubeObjectScope, + KubeJsonApiListMetadata, + KubeJsonApiObjectMetadata, + KubeJsonApiDataList, +} from "./api-types"; +import type { KubeObject } from "./kube-object"; + +const resourceApplierAnnotationsForFiltering = ["kubectl.kubernetes.io/last-applied-configuration"]; + +export const filterOutResourceApplierAnnotations = (label: string) => + !resourceApplierAnnotationsForFiltering.some((key) => label.startsWith(key)); + +export function isKubeObjectNonSystem( + item: KubeJsonApiData | KubeObject, unknown, unknown>, +) { + return !item.metadata.name?.startsWith("system:"); +} + +export function isJsonApiData(object: unknown): object is KubeJsonApiData { + return ( + isObject(object) && + hasTypedProperty(object, "kind", isString) && + hasTypedProperty(object, "apiVersion", isString) && + hasTypedProperty(object, "metadata", isKubeJsonApiMetadata) + ); +} + +export function isKubeJsonApiListMetadata(object: unknown): object is KubeJsonApiListMetadata { + return ( + isObject(object) && + hasOptionalTypedProperty(object, "resourceVersion", isString) && + hasOptionalTypedProperty(object, "selfLink", isString) + ); +} + +export function isKubeJsonApiMetadata(object: unknown): object is KubeJsonApiObjectMetadata { + return ( + isObject(object) && + hasTypedProperty(object, "uid", isString) && + hasTypedProperty(object, "name", isString) && + hasTypedProperty(object, "resourceVersion", isString) && + hasOptionalTypedProperty(object, "selfLink", isString) && + hasOptionalTypedProperty(object, "namespace", isString) && + hasOptionalTypedProperty(object, "creationTimestamp", isString) && + hasOptionalTypedProperty(object, "continue", isString) && + hasOptionalTypedProperty(object, "finalizers", bindPredicate(isTypedArray, isString)) && + hasOptionalTypedProperty(object, "labels", bindPredicate(isRecord, isString, isString)) && + hasOptionalTypedProperty(object, "annotations", bindPredicate(isRecord, isString, isString)) + ); +} + +export function isPartialJsonApiMetadata(object: unknown): object is Partial { + return ( + isObject(object) && + hasOptionalTypedProperty(object, "uid", isString) && + hasOptionalTypedProperty(object, "name", isString) && + hasOptionalTypedProperty(object, "resourceVersion", isString) && + hasOptionalTypedProperty(object, "selfLink", isString) && + hasOptionalTypedProperty(object, "namespace", isString) && + hasOptionalTypedProperty(object, "creationTimestamp", isString) && + hasOptionalTypedProperty(object, "continue", isString) && + hasOptionalTypedProperty(object, "finalizers", bindPredicate(isTypedArray, isString)) && + hasOptionalTypedProperty(object, "labels", bindPredicate(isRecord, isString, isString)) && + hasOptionalTypedProperty(object, "annotations", bindPredicate(isRecord, isString, isString)) + ); +} + +export function isPartialJsonApiData(object: unknown): object is Partial { + return ( + isObject(object) && + hasOptionalTypedProperty(object, "kind", isString) && + hasOptionalTypedProperty(object, "apiVersion", isString) && + hasOptionalTypedProperty(object, "metadata", isPartialJsonApiMetadata) + ); +} + +export function isJsonApiDataList( + object: unknown, + verifyItem: (val: unknown) => val is T, +): object is KubeJsonApiDataList { + return ( + isObject(object) && + hasTypedProperty(object, "kind", isString) && + hasTypedProperty(object, "apiVersion", isString) && + hasTypedProperty(object, "metadata", isKubeJsonApiListMetadata) && + hasTypedProperty(object, "items", bindPredicate(isTypedArray, verifyItem)) + ); +} + +export function stringifyLabels(labels?: Partial>): string[] { + if (!labels) { + return []; + } + + return Object.entries(labels).map(([name, value]) => `${name}=${value}`); +} diff --git a/packages/kube-object/tsconfig.json b/packages/kube-object/tsconfig.json new file mode 100644 index 0000000000..1819203dc1 --- /dev/null +++ b/packages/kube-object/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "@k8slens/typescript/config/base.json", + "include": ["**/*.ts"] +} diff --git a/packages/kube-object/webpack.config.js b/packages/kube-object/webpack.config.js new file mode 100644 index 0000000000..b54738d0a7 --- /dev/null +++ b/packages/kube-object/webpack.config.js @@ -0,0 +1 @@ +module.exports = require("@k8slens/webpack").configForReact; \ No newline at end of file