mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Merge remote-tracking branch 'origin/master' into eviction_api_support_for_pods
# Conflicts: # packages/core/src/common/k8s-api/__tests__/kube-api.test.ts
This commit is contained in:
commit
4b78e82dfb
@ -15,6 +15,8 @@ All releases will be made by creating a PR which bumps the version field in the
|
|||||||
1. If you are making a patch release (or a prerelease for one) make sure you are on the `release/v<MAJOR>.<MINOR>` branch.
|
1. If you are making a patch release (or a prerelease for one) make sure you are on the `release/v<MAJOR>.<MINOR>` branch.
|
||||||
1. Run `npm run create-release-pr`.
|
1. Run `npm run create-release-pr`.
|
||||||
1. Pick the PRs that you want to include in this release using the keys listed.
|
1. Pick the PRs that you want to include in this release using the keys listed.
|
||||||
|
- If you are making a patch release this might include fixing up some cherry-picking of commits. These actions should be done in a separate terminal.
|
||||||
|
- If a package version is having a major version bump then `npm` will complain about `peerDependency` conflicts. These will have to be fixed up separately.
|
||||||
1. Once the PR is created, approved, and then merged the `Release Open Lens` workflow will create a tag and release for you.
|
1. Once the PR is created, approved, and then merged the `Release Open Lens` workflow will create a tag and release for you.
|
||||||
1. If you are making a major or minor release, create a `release/v<MAJOR>.<MINOR>` branch and push it to `origin` so that future patch releases can be made from it.
|
1. If you are making a major or minor release, create a `release/v<MAJOR>.<MINOR>` branch and push it to `origin` so that future patch releases can be made from it.
|
||||||
1. If you released a major or minor version, create a new patch milestone and move all bug issues to that milestone and all enhancement issues to the next minor milestone.
|
1. If you released a major or minor version, create a new patch milestone and move all bug issues to that milestone and all enhancement issues to the next minor milestone.
|
||||||
|
|||||||
713
package-lock.json
generated
713
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -19,6 +19,7 @@
|
|||||||
"clean:node_modules": "lerna clean -y && rimraf node_modules",
|
"clean:node_modules": "lerna clean -y && rimraf node_modules",
|
||||||
"dev": "lerna run dev --stream --skip-nx-cache",
|
"dev": "lerna run dev --stream --skip-nx-cache",
|
||||||
"lint": "lerna run lint --stream",
|
"lint": "lerna run lint --stream",
|
||||||
|
"lint:fix": "lerna run lint:fix --stream",
|
||||||
"mkdocs:serve-local": "docker build -t mkdocs-serve-local:latest mkdocs/ && docker run --rm -it -p 8000:8000 -v ${PWD}:/docs mkdocs-serve-local:latest",
|
"mkdocs:serve-local": "docker build -t mkdocs-serve-local:latest mkdocs/ && docker run --rm -it -p 8000:8000 -v ${PWD}:/docs mkdocs-serve-local:latest",
|
||||||
"mkdocs:verify": "docker build -t mkdocs-serve-local:latest mkdocs/ && docker run --rm -v ${PWD}:/docs mkdocs-serve-local:latest build --strict",
|
"mkdocs:verify": "docker build -t mkdocs-serve-local:latest mkdocs/ && docker run --rm -v ${PWD}:/docs mkdocs-serve-local:latest build --strict",
|
||||||
"test:unit": "lerna run --stream test:unit",
|
"test:unit": "lerna run --stream test:unit",
|
||||||
@ -28,6 +29,11 @@
|
|||||||
"precreate-release-pr": "cd packages/release-tool && npm run build",
|
"precreate-release-pr": "cd packages/release-tool && npm run build",
|
||||||
"create-release-pr": "node packages/release-tool/dist/index.js"
|
"create-release-pr": "node packages/release-tool/dist/index.js"
|
||||||
},
|
},
|
||||||
|
"overrides": {
|
||||||
|
"underscore": "^1.12.1",
|
||||||
|
"react": "^17",
|
||||||
|
"@types/react": "^17"
|
||||||
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"adr": "^1.4.3",
|
"adr": "^1.4.3",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
|
|||||||
9
packages/cluster-settings/.swcrc
Normal file
9
packages/cluster-settings/.swcrc
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/swcrc",
|
||||||
|
"jsc": {
|
||||||
|
"parser": {
|
||||||
|
"syntax": "typescript"
|
||||||
|
},
|
||||||
|
"target": "es2022"
|
||||||
|
}
|
||||||
|
}
|
||||||
3
packages/cluster-settings/README.md
Normal file
3
packages/cluster-settings/README.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# Description
|
||||||
|
|
||||||
|
The package exports tokens needed for external configuration of Cluster Settings page.
|
||||||
31
packages/cluster-settings/package.json
Normal file
31
packages/cluster-settings/package.json
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"name": "@k8slens/cluster-settings",
|
||||||
|
"version": "6.5.0-alpha.1",
|
||||||
|
"description": "Injection token exporter for cluster settings configuration",
|
||||||
|
"license": "MIT",
|
||||||
|
"private": false,
|
||||||
|
"mode": "production",
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public",
|
||||||
|
"registry": "https://registry.npmjs.org/"
|
||||||
|
},
|
||||||
|
"main": "./dist/index.js",
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"files": [
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"clean": "rimraf dist/",
|
||||||
|
"generate-types": "tsc --d --declarationDir ./dist --declarationMap --emitDeclarationOnly",
|
||||||
|
"build": "npm run generate-types && swc ./src/index.ts -d ./dist",
|
||||||
|
"prepare:test": "npm run build"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@ogre-tools/injectable": "^15.1.2",
|
||||||
|
"@swc/cli": "^0.1.61",
|
||||||
|
"@swc/core": "^1.3.37",
|
||||||
|
"@types/node": "^16.18.11",
|
||||||
|
"@types/semver": "^7.3.13",
|
||||||
|
"rimraf": "^4.1.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
30
packages/cluster-settings/src/index.ts
Normal file
30
packages/cluster-settings/src/index.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { getInjectionToken } from "@ogre-tools/injectable";
|
||||||
|
|
||||||
|
type ClusterPreferences = {
|
||||||
|
clusterName?: string;
|
||||||
|
icon?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ClusterIconMenuItem {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
disabled?: (preferences: ClusterPreferences) => boolean;
|
||||||
|
onClick: (preferences: ClusterPreferences) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ClusterIconSettingComponentProps {
|
||||||
|
preferences: ClusterPreferences;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ClusterIconSettingsComponent {
|
||||||
|
id: string;
|
||||||
|
Component: React.ComponentType<ClusterIconSettingComponentProps>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const clusterIconSettingsMenuInjectionToken = getInjectionToken<ClusterIconMenuItem>({
|
||||||
|
id: "cluster-icon-settings-menu-injection-token",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const clusterIconSettingsComponentInjectionToken = getInjectionToken<ClusterIconSettingsComponent>({
|
||||||
|
id: "cluster-icon-settings-component-injection-token",
|
||||||
|
});
|
||||||
18
packages/cluster-settings/tsconfig.json
Normal file
18
packages/cluster-settings/tsconfig.json
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "dist/",
|
||||||
|
"paths": {
|
||||||
|
"*": [
|
||||||
|
"node_modules/*",
|
||||||
|
"types/*"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src/**/*",
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"node_modules",
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -116,13 +116,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"resolutions": {
|
|
||||||
"@astronautlabs/jsonpath/underscore": "^1.12.1"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astronautlabs/jsonpath": "^1.1.0",
|
"@astronautlabs/jsonpath": "^1.1.0",
|
||||||
"@hapi/call": "^9.0.1",
|
"@hapi/call": "^9.0.1",
|
||||||
"@hapi/subtext": "^7.1.0",
|
"@hapi/subtext": "^7.1.0",
|
||||||
|
"@k8slens/cluster-settings": "^6.5.0-alpha.1",
|
||||||
"@k8slens/node-fetch": "^6.5.0-alpha.1",
|
"@k8slens/node-fetch": "^6.5.0-alpha.1",
|
||||||
"@kubernetes/client-node": "^0.18.1",
|
"@kubernetes/client-node": "^0.18.1",
|
||||||
"@material-ui/styles": "^4.11.5",
|
"@material-ui/styles": "^4.11.5",
|
||||||
@ -134,7 +132,6 @@
|
|||||||
"@sentry/electron": "^3.0.8",
|
"@sentry/electron": "^3.0.8",
|
||||||
"@sentry/integrations": "^6.19.3",
|
"@sentry/integrations": "^6.19.3",
|
||||||
"@side/jest-runtime": "^1.1.0",
|
"@side/jest-runtime": "^1.1.0",
|
||||||
"abort-controller": "^3.0.0",
|
|
||||||
"auto-bind": "^4.0.0",
|
"auto-bind": "^4.0.0",
|
||||||
"await-lock": "^2.2.2",
|
"await-lock": "^2.2.2",
|
||||||
"byline": "^5.0.0",
|
"byline": "^5.0.0",
|
||||||
@ -193,6 +190,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@async-fn/jest": "1.6.4",
|
"@async-fn/jest": "1.6.4",
|
||||||
|
"@k8slens/messaging-fake-bridge": "^1.0.0-alpha.1",
|
||||||
"@material-ui/core": "^4.12.3",
|
"@material-ui/core": "^4.12.3",
|
||||||
"@material-ui/icons": "^4.11.2",
|
"@material-ui/icons": "^4.11.2",
|
||||||
"@material-ui/lab": "^4.0.0-alpha.60",
|
"@material-ui/lab": "^4.0.0-alpha.60",
|
||||||
@ -223,7 +221,6 @@
|
|||||||
"@types/marked": "^4.0.8",
|
"@types/marked": "^4.0.8",
|
||||||
"@types/md5-file": "^4.0.2",
|
"@types/md5-file": "^4.0.2",
|
||||||
"@types/memorystream": "^0.3.0",
|
"@types/memorystream": "^0.3.0",
|
||||||
"@types/mini-css-extract-plugin": "^2.4.0",
|
|
||||||
"@types/mock-fs": "^4.13.1",
|
"@types/mock-fs": "^4.13.1",
|
||||||
"@types/node": "^16.18.11",
|
"@types/node": "^16.18.11",
|
||||||
"@types/proper-lockfile": "^4.1.2",
|
"@types/proper-lockfile": "^4.1.2",
|
||||||
@ -240,7 +237,6 @@
|
|||||||
"@types/semver": "^7.3.13",
|
"@types/semver": "^7.3.13",
|
||||||
"@types/tar": "^6.1.4",
|
"@types/tar": "^6.1.4",
|
||||||
"@types/tcp-port-used": "^1.0.1",
|
"@types/tcp-port-used": "^1.0.1",
|
||||||
"@types/tempy": "^0.3.0",
|
|
||||||
"@types/triple-beam": "^1.3.2",
|
"@types/triple-beam": "^1.3.2",
|
||||||
"@types/url-parse": "^1.4.8",
|
"@types/url-parse": "^1.4.8",
|
||||||
"@types/uuid": "^8.3.4",
|
"@types/uuid": "^8.3.4",
|
||||||
@ -262,7 +258,7 @@
|
|||||||
"css-loader": "^6.7.3",
|
"css-loader": "^6.7.3",
|
||||||
"deepdash": "^5.3.9",
|
"deepdash": "^5.3.9",
|
||||||
"dompurify": "^2.4.4",
|
"dompurify": "^2.4.4",
|
||||||
"electron": "^19.1.9",
|
"electron": "^22.3.3",
|
||||||
"electron-builder": "^23.6.0",
|
"electron-builder": "^23.6.0",
|
||||||
"esbuild": "^0.17.8",
|
"esbuild": "^0.17.8",
|
||||||
"esbuild-loader": "^2.21.0",
|
"esbuild-loader": "^2.21.0",
|
||||||
@ -328,7 +324,11 @@
|
|||||||
"@k8slens/application": "^6.5.0-alpha.0",
|
"@k8slens/application": "^6.5.0-alpha.0",
|
||||||
"@k8slens/application-for-electron-main": "^6.5.0-alpha.0",
|
"@k8slens/application-for-electron-main": "^6.5.0-alpha.0",
|
||||||
"@k8slens/legacy-extensions": "^1.0.0-alpha.0",
|
"@k8slens/legacy-extensions": "^1.0.0-alpha.0",
|
||||||
|
"@k8slens/messaging": "^1.0.0-alpha.1",
|
||||||
|
"@k8slens/messaging-for-main": "^1.0.0-alpha.1",
|
||||||
|
"@k8slens/messaging-for-renderer": "^1.0.0-alpha.1",
|
||||||
"@k8slens/run-many": "^1.0.0-alpha.1",
|
"@k8slens/run-many": "^1.0.0-alpha.1",
|
||||||
|
"@k8slens/startable-stoppable": "^1.0.0-alpha.1",
|
||||||
"@k8slens/test-utils": "^1.0.0-alpha.1",
|
"@k8slens/test-utils": "^1.0.0-alpha.1",
|
||||||
"@k8slens/utilities": "^1.0.0-alpha.1",
|
"@k8slens/utilities": "^1.0.0-alpha.1",
|
||||||
"@types/byline": "^4.2.33",
|
"@types/byline": "^4.2.33",
|
||||||
|
|||||||
@ -11,7 +11,7 @@ export const pathNames: PathName[] = [
|
|||||||
"home",
|
"home",
|
||||||
"appData",
|
"appData",
|
||||||
"userData",
|
"userData",
|
||||||
"cache",
|
"sessionData",
|
||||||
"temp",
|
"temp",
|
||||||
"exe",
|
"exe",
|
||||||
"module",
|
"module",
|
||||||
|
|||||||
@ -3,11 +3,7 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import type { AppPaths } from "./app-path-injection-token";
|
import type { AppPaths } from "./app-path-injection-token";
|
||||||
import type { RequestChannel } from "../utils/channel/request-channel-listener-injection-token";
|
import { getRequestChannel } from "@k8slens/messaging";
|
||||||
|
|
||||||
export type AppPathsChannel = RequestChannel<void, AppPaths>;
|
export const appPathsChannel = getRequestChannel<void, AppPaths>("app-paths");
|
||||||
|
|
||||||
export const appPathsChannel: AppPathsChannel = {
|
|
||||||
id: "app-paths",
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|||||||
@ -21,7 +21,6 @@ describe("app-paths", () => {
|
|||||||
const defaultAppPathsStub: AppPaths = {
|
const defaultAppPathsStub: AppPaths = {
|
||||||
currentApp: "/some-current-app",
|
currentApp: "/some-current-app",
|
||||||
appData: "/some-app-data",
|
appData: "/some-app-data",
|
||||||
cache: "/some-cache",
|
|
||||||
crashDumps: "/some-crash-dumps",
|
crashDumps: "/some-crash-dumps",
|
||||||
desktop: "/some-desktop",
|
desktop: "/some-desktop",
|
||||||
documents: "/some-documents",
|
documents: "/some-documents",
|
||||||
@ -36,6 +35,7 @@ describe("app-paths", () => {
|
|||||||
temp: "/some-temp",
|
temp: "/some-temp",
|
||||||
videos: "/some-videos",
|
videos: "/some-videos",
|
||||||
userData: "/some-irrelevant-user-data",
|
userData: "/some-irrelevant-user-data",
|
||||||
|
sessionData: "/some-irrelevant-user-data", // By default this points to userData
|
||||||
};
|
};
|
||||||
|
|
||||||
builder.beforeApplicationStart(({ mainDi }) => {
|
builder.beforeApplicationStart(({ mainDi }) => {
|
||||||
@ -73,7 +73,6 @@ describe("app-paths", () => {
|
|||||||
expect(actual).toEqual({
|
expect(actual).toEqual({
|
||||||
currentApp: "/some-current-app",
|
currentApp: "/some-current-app",
|
||||||
appData: "/some-app-data",
|
appData: "/some-app-data",
|
||||||
cache: "/some-cache",
|
|
||||||
crashDumps: "/some-crash-dumps",
|
crashDumps: "/some-crash-dumps",
|
||||||
desktop: "/some-desktop",
|
desktop: "/some-desktop",
|
||||||
documents: "/some-documents",
|
documents: "/some-documents",
|
||||||
@ -88,6 +87,7 @@ describe("app-paths", () => {
|
|||||||
temp: "/some-temp",
|
temp: "/some-temp",
|
||||||
videos: "/some-videos",
|
videos: "/some-videos",
|
||||||
userData: "/some-app-data/some-product-name",
|
userData: "/some-app-data/some-product-name",
|
||||||
|
sessionData: "/some-app-data/some-product-name",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -97,7 +97,6 @@ describe("app-paths", () => {
|
|||||||
expect(actual).toEqual({
|
expect(actual).toEqual({
|
||||||
currentApp: "/some-current-app",
|
currentApp: "/some-current-app",
|
||||||
appData: "/some-app-data",
|
appData: "/some-app-data",
|
||||||
cache: "/some-cache",
|
|
||||||
crashDumps: "/some-crash-dumps",
|
crashDumps: "/some-crash-dumps",
|
||||||
desktop: "/some-desktop",
|
desktop: "/some-desktop",
|
||||||
documents: "/some-documents",
|
documents: "/some-documents",
|
||||||
@ -112,6 +111,7 @@ describe("app-paths", () => {
|
|||||||
temp: "/some-temp",
|
temp: "/some-temp",
|
||||||
videos: "/some-videos",
|
videos: "/some-videos",
|
||||||
userData: "/some-app-data/some-product-name",
|
userData: "/some-app-data/some-product-name",
|
||||||
|
sessionData: "/some-app-data/some-product-name",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -15,7 +15,7 @@ import type { GetConfigurationFileModel } from "../get-configuration-file-model/
|
|||||||
import type { Logger } from "../logger";
|
import type { Logger } from "../logger";
|
||||||
import type { PersistStateToConfig } from "./save-to-file";
|
import type { PersistStateToConfig } from "./save-to-file";
|
||||||
import type { GetBasenameOfPath } from "../path/get-basename.injectable";
|
import type { GetBasenameOfPath } from "../path/get-basename.injectable";
|
||||||
import type { EnlistMessageChannelListener } from "../utils/channel/enlist-message-channel-listener-injection-token";
|
import type { EnlistMessageChannelListener } from "@k8slens/messaging";
|
||||||
import { toJS } from "../utils";
|
import { toJS } from "../utils";
|
||||||
|
|
||||||
export interface BaseStoreParams<T> extends Omit<ConfOptions<T>, "migrations"> {
|
export interface BaseStoreParams<T> extends Omit<ConfOptions<T>, "migrations"> {
|
||||||
@ -108,6 +108,7 @@ export abstract class BaseStore<T extends object> {
|
|||||||
this.params.syncOptions,
|
this.params.syncOptions,
|
||||||
),
|
),
|
||||||
this.dependencies.enlistMessageChannelListener({
|
this.dependencies.enlistMessageChannelListener({
|
||||||
|
id: this.displayName,
|
||||||
channel: {
|
channel: {
|
||||||
id: `${this.dependencies.ipcChannelPrefixes.local}:${config.path}`,
|
id: `${this.dependencies.ipcChannelPrefixes.local}:${config.path}`,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -4,6 +4,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import type { SelfSignedCert } from "selfsigned";
|
import type { SelfSignedCert } from "selfsigned";
|
||||||
import { getRequestChannel } from "../utils/channel/get-request-channel";
|
import { getRequestChannel } from "@k8slens/messaging";
|
||||||
|
|
||||||
export const lensProxyCertificateChannel = getRequestChannel<void, SelfSignedCert>("request-lens-proxy-certificate");
|
export const lensProxyCertificateChannel = getRequestChannel<void, SelfSignedCert>("request-lens-proxy-certificate");
|
||||||
|
|||||||
@ -16,7 +16,7 @@ import { baseStoreIpcChannelPrefixesInjectionToken } from "../base-store/channel
|
|||||||
import { shouldBaseStoreDisableSyncInIpcListenerInjectionToken } from "../base-store/disable-sync";
|
import { shouldBaseStoreDisableSyncInIpcListenerInjectionToken } from "../base-store/disable-sync";
|
||||||
import { persistStateToConfigInjectionToken } from "../base-store/save-to-file";
|
import { persistStateToConfigInjectionToken } from "../base-store/save-to-file";
|
||||||
import getBasenameOfPathInjectable from "../path/get-basename.injectable";
|
import getBasenameOfPathInjectable from "../path/get-basename.injectable";
|
||||||
import { enlistMessageChannelListenerInjectionToken } from "../utils/channel/enlist-message-channel-listener-injection-token";
|
import { enlistMessageChannelListenerInjectionToken } from "@k8slens/messaging";
|
||||||
|
|
||||||
const clusterStoreInjectable = getInjectable({
|
const clusterStoreInjectable = getInjectable({
|
||||||
id: "cluster-store",
|
id: "cluster-store",
|
||||||
|
|||||||
@ -1,50 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import type { KubeConfig, V1ResourceAttributes } from "@kubernetes/client-node";
|
|
||||||
import { AuthorizationV1Api } from "@kubernetes/client-node";
|
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
|
||||||
import loggerInjectable from "../logger.injectable";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Requests the permissions for actions on the kube cluster
|
|
||||||
* @param resourceAttributes The descriptor of the action that is desired to be known if it is allowed
|
|
||||||
* @returns `true` if the actions described are allowed
|
|
||||||
*/
|
|
||||||
export type CanI = (resourceAttributes: V1ResourceAttributes) => Promise<boolean>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param proxyConfig This config's `currentContext` field must be set, and will be used as the target cluster
|
|
||||||
*/
|
|
||||||
export type CreateAuthorizationReview = (proxyConfig: KubeConfig) => CanI;
|
|
||||||
|
|
||||||
const createAuthorizationReviewInjectable = getInjectable({
|
|
||||||
id: "authorization-review",
|
|
||||||
instantiate: (di): CreateAuthorizationReview => {
|
|
||||||
const logger = di.inject(loggerInjectable);
|
|
||||||
|
|
||||||
return (proxyConfig) => {
|
|
||||||
const api = proxyConfig.makeApiClient(AuthorizationV1Api);
|
|
||||||
|
|
||||||
return async (resourceAttributes: V1ResourceAttributes): Promise<boolean> => {
|
|
||||||
try {
|
|
||||||
const { body } = await api.createSelfSubjectAccessReview({
|
|
||||||
apiVersion: "authorization.k8s.io/v1",
|
|
||||||
kind: "SelfSubjectAccessReview",
|
|
||||||
spec: { resourceAttributes },
|
|
||||||
});
|
|
||||||
|
|
||||||
return body.status?.allowed ?? false;
|
|
||||||
} catch (error) {
|
|
||||||
logger.error(`[AUTHORIZATION-REVIEW]: failed to create access review: ${error}`, { resourceAttributes });
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default createAuthorizationReviewInjectable;
|
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { AuthorizationV1Api } from "@kubernetes/client-node";
|
||||||
|
import type { KubeConfig } from "@kubernetes/client-node";
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
|
||||||
|
export type CreateAuthorizationApi = (config: KubeConfig) => AuthorizationV1Api;
|
||||||
|
|
||||||
|
const createAuthorizationApiInjectable = getInjectable({
|
||||||
|
id: "create-authorization-api",
|
||||||
|
instantiate: (): CreateAuthorizationApi => (config) => config.makeApiClient(AuthorizationV1Api),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default createAuthorizationApiInjectable;
|
||||||
42
packages/core/src/common/cluster/create-can-i.injectable.ts
Normal file
42
packages/core/src/common/cluster/create-can-i.injectable.ts
Normal file
@ -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 { AuthorizationV1Api, V1ResourceAttributes } from "@kubernetes/client-node";
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import loggerInjectable from "../logger.injectable";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Requests the permissions for actions on the kube cluster
|
||||||
|
* @param resourceAttributes The descriptor of the action that is desired to be known if it is allowed
|
||||||
|
* @returns `true` if the actions described are allowed
|
||||||
|
*/
|
||||||
|
export type CanI = (resourceAttributes: V1ResourceAttributes) => Promise<boolean>;
|
||||||
|
|
||||||
|
export type CreateCanI = (api: AuthorizationV1Api) => CanI;
|
||||||
|
|
||||||
|
const createCanIInjectable = getInjectable({
|
||||||
|
id: "create-can-i",
|
||||||
|
instantiate: (di): CreateCanI => {
|
||||||
|
const logger = di.inject(loggerInjectable);
|
||||||
|
|
||||||
|
return (api) => async (resourceAttributes: V1ResourceAttributes): Promise<boolean> => {
|
||||||
|
try {
|
||||||
|
const { body } = await api.createSelfSubjectAccessReview({
|
||||||
|
apiVersion: "authorization.k8s.io/v1",
|
||||||
|
kind: "SelfSubjectAccessReview",
|
||||||
|
spec: { resourceAttributes },
|
||||||
|
});
|
||||||
|
|
||||||
|
return body.status?.allowed ?? false;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`[AUTHORIZATION-REVIEW]: failed to create access review: ${error}`, { resourceAttributes });
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default createCanIInjectable;
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import type { KubeConfig } from "@kubernetes/client-node";
|
||||||
|
import { CoreV1Api } from "@kubernetes/client-node";
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
|
||||||
|
export type CreateCoreApi = (config: KubeConfig) => CoreV1Api;
|
||||||
|
|
||||||
|
const createCoreApiInjectable = getInjectable({
|
||||||
|
id: "create-core-api",
|
||||||
|
instantiate: (): CreateCoreApi => config => config.makeApiClient(CoreV1Api),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default createCoreApiInjectable;
|
||||||
@ -0,0 +1,57 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { AuthorizationV1Api } from "@kubernetes/client-node";
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import loggerInjectable from "../logger.injectable";
|
||||||
|
import type { KubeApiResource } from "../rbac";
|
||||||
|
|
||||||
|
export type CanListResource = (resource: KubeApiResource) => boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Requests the permissions for actions on the kube cluster
|
||||||
|
* @param namespace The namespace of the resources
|
||||||
|
*/
|
||||||
|
export type RequestNamespaceListPermissions = (namespace: string) => Promise<CanListResource>;
|
||||||
|
|
||||||
|
export type CreateRequestNamespaceListPermissions = (api: AuthorizationV1Api) => RequestNamespaceListPermissions;
|
||||||
|
|
||||||
|
const createRequestNamespaceListPermissionsInjectable = getInjectable({
|
||||||
|
id: "create-request-namespace-list-permissions",
|
||||||
|
instantiate: (di): CreateRequestNamespaceListPermissions => {
|
||||||
|
const logger = di.inject(loggerInjectable);
|
||||||
|
|
||||||
|
return (api) => async (namespace) => {
|
||||||
|
try {
|
||||||
|
const { body: { status }} = await api.createSelfSubjectRulesReview({
|
||||||
|
apiVersion: "authorization.k8s.io/v1",
|
||||||
|
kind: "SelfSubjectRulesReview",
|
||||||
|
spec: { namespace },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!status || status.incomplete) {
|
||||||
|
logger.warn(`[AUTHORIZATION-NAMESPACE-REVIEW]: allowing all resources in namespace="${namespace}" due to incomplete SelfSubjectRulesReview: ${status?.evaluationError}`);
|
||||||
|
|
||||||
|
return () => true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { resourceRules } = status;
|
||||||
|
|
||||||
|
return (resource) => (
|
||||||
|
resourceRules
|
||||||
|
.filter(({ apiGroups = ["*"] }) => apiGroups.includes("*") || apiGroups.includes(resource.group))
|
||||||
|
.filter(({ resources = ["*"] }) => resources.includes("*") || resources.includes(resource.apiName))
|
||||||
|
.some(({ verbs }) => verbs.includes("*") || verbs.includes("list"))
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`[AUTHORIZATION-NAMESPACE-REVIEW]: failed to create subject rules review`, { namespace, error });
|
||||||
|
|
||||||
|
return () => true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default createRequestNamespaceListPermissionsInjectable;
|
||||||
@ -4,7 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import type { ClusterId } from "../cluster-types";
|
import type { ClusterId } from "../cluster-types";
|
||||||
import type { MessageChannel } from "../utils/channel/message-channel-listener-injection-token";
|
import type { MessageChannel } from "@k8slens/messaging";
|
||||||
|
|
||||||
export const currentClusterMessageChannel: MessageChannel<ClusterId> = {
|
export const currentClusterMessageChannel: MessageChannel<ClusterId> = {
|
||||||
id: "current-visible-cluster",
|
id: "current-visible-cluster",
|
||||||
|
|||||||
@ -2,27 +2,21 @@
|
|||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import type { KubeConfig } from "@kubernetes/client-node";
|
import type { CoreV1Api } from "@kubernetes/client-node";
|
||||||
import { CoreV1Api } from "@kubernetes/client-node";
|
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { isDefined } from "@k8slens/utilities";
|
import { isDefined } from "@k8slens/utilities";
|
||||||
|
|
||||||
export type ListNamespaces = () => Promise<string[]>;
|
export type ListNamespaces = () => Promise<string[]>;
|
||||||
|
export type CreateListNamespaces = (api: CoreV1Api) => ListNamespaces;
|
||||||
export type CreateListNamespaces = (config: KubeConfig) => ListNamespaces;
|
|
||||||
|
|
||||||
const createListNamespacesInjectable = getInjectable({
|
const createListNamespacesInjectable = getInjectable({
|
||||||
id: "create-list-namespaces",
|
id: "create-list-namespaces",
|
||||||
instantiate: (): CreateListNamespaces => (config) => {
|
instantiate: (): CreateListNamespaces => (api) => async () => {
|
||||||
const coreApi = config.makeApiClient(CoreV1Api);
|
const { body: { items }} = await api.listNamespace();
|
||||||
|
|
||||||
return async () => {
|
return items
|
||||||
const { body: { items }} = await coreApi.listNamespace();
|
.map(ns => ns.metadata?.name)
|
||||||
|
.filter(isDefined);
|
||||||
return items
|
|
||||||
.map(ns => ns.metadata?.name)
|
|
||||||
.filter(isDefined);
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -1,72 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import type { KubeConfig } from "@kubernetes/client-node";
|
|
||||||
import { AuthorizationV1Api } from "@kubernetes/client-node";
|
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
|
||||||
import loggerInjectable from "../logger.injectable";
|
|
||||||
import type { KubeApiResource } from "../rbac";
|
|
||||||
|
|
||||||
export type CanListResource = (resource: KubeApiResource) => boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Requests the permissions for actions on the kube cluster
|
|
||||||
* @param namespace The namespace of the resources
|
|
||||||
*/
|
|
||||||
export type RequestNamespaceListPermissions = (namespace: string) => Promise<CanListResource>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param proxyConfig This config's `currentContext` field must be set, and will be used as the target cluster
|
|
||||||
*/
|
|
||||||
export type RequestNamespaceListPermissionsFor = (proxyConfig: KubeConfig) => RequestNamespaceListPermissions;
|
|
||||||
|
|
||||||
const requestNamespaceListPermissionsForInjectable = getInjectable({
|
|
||||||
id: "request-namespace-list-permissions-for",
|
|
||||||
instantiate: (di): RequestNamespaceListPermissionsFor => {
|
|
||||||
const logger = di.inject(loggerInjectable);
|
|
||||||
|
|
||||||
return (proxyConfig) => {
|
|
||||||
const api = proxyConfig.makeApiClient(AuthorizationV1Api);
|
|
||||||
|
|
||||||
return async (namespace) => {
|
|
||||||
try {
|
|
||||||
const { body: { status }} = await api.createSelfSubjectRulesReview({
|
|
||||||
apiVersion: "authorization.k8s.io/v1",
|
|
||||||
kind: "SelfSubjectRulesReview",
|
|
||||||
spec: { namespace },
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!status || status.incomplete) {
|
|
||||||
logger.warn(`[AUTHORIZATION-NAMESPACE-REVIEW]: allowing all resources in namespace="${namespace}" due to incomplete SelfSubjectRulesReview: ${status?.evaluationError}`);
|
|
||||||
|
|
||||||
return () => true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { resourceRules } = status;
|
|
||||||
|
|
||||||
return (resource) => {
|
|
||||||
const rules = resourceRules.filter(({
|
|
||||||
apiGroups = ["*"],
|
|
||||||
resources = ["*"],
|
|
||||||
}) => {
|
|
||||||
const isAboutRelevantApiGroup = apiGroups.includes("*") || apiGroups.includes(resource.group);
|
|
||||||
const isAboutResource = resources.includes("*") || resources.includes(resource.apiName);
|
|
||||||
|
|
||||||
return isAboutRelevantApiGroup && isAboutResource;
|
|
||||||
});
|
|
||||||
|
|
||||||
return rules.some(({ verbs }) => verbs.includes("*") || verbs.includes("list"));
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
logger.error(`[AUTHORIZATION-NAMESPACE-REVIEW]: failed to create subject rules review`, { namespace, error });
|
|
||||||
|
|
||||||
return () => true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default requestNamespaceListPermissionsForInjectable;
|
|
||||||
@ -3,334 +3,225 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { V1SubjectRulesReviewStatus } from "@kubernetes/client-node";
|
import type { AsyncFnMock } from "@async-fn/jest";
|
||||||
|
import asyncFn from "@async-fn/jest";
|
||||||
|
import type { AuthorizationV1Api, V1SubjectRulesReviewStatus } from "@kubernetes/client-node";
|
||||||
import type { DiContainer } from "@ogre-tools/injectable";
|
import type { DiContainer } from "@ogre-tools/injectable";
|
||||||
|
import type { IncomingMessage } from "http";
|
||||||
|
import { anyObject } from "jest-mock-extended";
|
||||||
import { getDiForUnitTesting } from "../../main/getDiForUnitTesting";
|
import { getDiForUnitTesting } from "../../main/getDiForUnitTesting";
|
||||||
import type { RequestNamespaceListPermissionsFor } from "./request-namespace-list-permissions.injectable";
|
import { cast } from "../../test-utils/cast";
|
||||||
import requestNamespaceListPermissionsForInjectable from "./request-namespace-list-permissions.injectable";
|
import type { KubeApiResource } from "../rbac";
|
||||||
|
import type { RequestNamespaceListPermissions } from "./create-request-namespace-list-permissions.injectable";
|
||||||
|
import createRequestNamespaceListPermissionsInjectable from "./create-request-namespace-list-permissions.injectable";
|
||||||
|
|
||||||
const createStubProxyConfig = (statusResponse: Promise<{ body: { status: V1SubjectRulesReviewStatus }}>) => ({
|
interface TestCase {
|
||||||
makeApiClient: () => ({
|
description: string;
|
||||||
createSelfSubjectRulesReview: (): Promise<{ body: { status: V1SubjectRulesReviewStatus }}> => statusResponse,
|
status: V1SubjectRulesReviewStatus;
|
||||||
}),
|
expected: boolean;
|
||||||
});
|
}
|
||||||
|
|
||||||
describe("requestNamespaceListPermissions", () => {
|
describe("requestNamespaceListPermissions", () => {
|
||||||
let di: DiContainer;
|
let di: DiContainer;
|
||||||
let requestNamespaceListPermissions: RequestNamespaceListPermissionsFor;
|
let createSelfSubjectRulesReviewMock: AsyncFnMock<AuthorizationV1Api["createSelfSubjectRulesReview"]>;
|
||||||
|
let requestNamespaceListPermissions: RequestNamespaceListPermissions;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
di = getDiForUnitTesting();
|
di = getDiForUnitTesting();
|
||||||
requestNamespaceListPermissions = di.inject(requestNamespaceListPermissionsForInjectable);
|
|
||||||
|
const createRequestNamespaceListPermissions = di.inject(createRequestNamespaceListPermissionsInjectable);
|
||||||
|
|
||||||
|
createSelfSubjectRulesReviewMock = asyncFn();
|
||||||
|
|
||||||
|
requestNamespaceListPermissions = createRequestNamespaceListPermissions(cast<AuthorizationV1Api>({
|
||||||
|
createSelfSubjectRulesReview: createSelfSubjectRulesReviewMock,
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("when api returns incomplete data", () => {
|
describe("when a request for list permissions in a namespace has been started", () => {
|
||||||
it("returns truthy function", async () => {
|
let request: ReturnType<RequestNamespaceListPermissions>;
|
||||||
const requestPermissions = requestNamespaceListPermissions(createStubProxyConfig(
|
|
||||||
new Promise((resolve) => resolve({
|
|
||||||
body: {
|
|
||||||
status: {
|
|
||||||
incomplete: true,
|
|
||||||
resourceRules: [],
|
|
||||||
nonResourceRules: [],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})),
|
|
||||||
) as any);
|
|
||||||
|
|
||||||
const permissionCheck = await requestPermissions("irrelevant-namespace");
|
beforeEach(() => {
|
||||||
|
request = requestNamespaceListPermissions("irrelevant-namespace");
|
||||||
expect(permissionCheck({
|
|
||||||
apiName: "pods",
|
|
||||||
group: "",
|
|
||||||
kind: "Pod",
|
|
||||||
namespaced: true,
|
|
||||||
})).toBeTruthy();
|
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
describe("when api rejects", () => {
|
it("should request the creation of a SelfSubjectRulesReview", () => {
|
||||||
it("returns truthy function", async () => {
|
expect(createSelfSubjectRulesReviewMock).toBeCalledWith(anyObject({
|
||||||
const requestPermissions = requestNamespaceListPermissions(createStubProxyConfig(
|
spec: {
|
||||||
new Promise((resolve, reject) => reject("unknown error")),
|
namespace: "irrelevant-namespace",
|
||||||
) as any);
|
},
|
||||||
|
}));
|
||||||
const permissionCheck = await requestPermissions("irrelevant-namespace");
|
|
||||||
|
|
||||||
expect(permissionCheck({
|
|
||||||
apiName: "pods",
|
|
||||||
group: "",
|
|
||||||
kind: "Pod",
|
|
||||||
namespaced: true,
|
|
||||||
})).toBeTruthy();
|
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
describe("when first resourceRule has all permissions for everything", () => {
|
([
|
||||||
it("return truthy function", async () => {
|
{
|
||||||
const requestPermissions = requestNamespaceListPermissions(createStubProxyConfig(
|
description: "incomplete data",
|
||||||
new Promise((resolve) => resolve({
|
status: {
|
||||||
body: {
|
incomplete: true,
|
||||||
status: {
|
resourceRules: [],
|
||||||
incomplete: false,
|
nonResourceRules: [],
|
||||||
resourceRules: [
|
},
|
||||||
{
|
expected: true,
|
||||||
apiGroups: ["*"],
|
},
|
||||||
verbs: ["*"],
|
{
|
||||||
},
|
description: "first resourceRule has all permissions for everything",
|
||||||
{
|
status: {
|
||||||
apiGroups: ["*"],
|
incomplete: false,
|
||||||
verbs: ["get"],
|
resourceRules: [
|
||||||
},
|
{
|
||||||
],
|
apiGroups: ["*"],
|
||||||
nonResourceRules: [],
|
verbs: ["*"],
|
||||||
},
|
},
|
||||||
},
|
{
|
||||||
})),
|
apiGroups: ["*"],
|
||||||
) as any);
|
verbs: ["get"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
nonResourceRules: [],
|
||||||
|
},
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "first resourceRule has list permissions for everything",
|
||||||
|
status: {
|
||||||
|
incomplete: false,
|
||||||
|
resourceRules: [
|
||||||
|
{
|
||||||
|
apiGroups: ["*"],
|
||||||
|
verbs: ["list"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
apiGroups: ["*"],
|
||||||
|
verbs: ["get"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
nonResourceRules: [],
|
||||||
|
},
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "first resourceRule has list permissions for asked resource",
|
||||||
|
status: {
|
||||||
|
incomplete: false,
|
||||||
|
resourceRules: [
|
||||||
|
{
|
||||||
|
apiGroups: ["some-api-group"],
|
||||||
|
resources: ["some-kind"],
|
||||||
|
verbs: ["list"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
apiGroups: ["*"],
|
||||||
|
verbs: ["get"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
nonResourceRules: [],
|
||||||
|
},
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "last resourceRule has all permissions for everything",
|
||||||
|
status: {
|
||||||
|
incomplete: false,
|
||||||
|
resourceRules: [
|
||||||
|
{
|
||||||
|
apiGroups: ["*"],
|
||||||
|
verbs: ["get"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
apiGroups: ["*"],
|
||||||
|
verbs: ["*"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
nonResourceRules: [],
|
||||||
|
},
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "last resourceRule has list permissions for asked resource",
|
||||||
|
status: {
|
||||||
|
incomplete: false,
|
||||||
|
resourceRules: [
|
||||||
|
{
|
||||||
|
apiGroups: ["*"],
|
||||||
|
verbs: ["get"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
apiGroups: ["some-api-group"],
|
||||||
|
resources: ["some-kind"],
|
||||||
|
verbs: ["list"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
nonResourceRules: [],
|
||||||
|
},
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "resourceRules has matching resource without list verb",
|
||||||
|
status: {
|
||||||
|
incomplete: false,
|
||||||
|
resourceRules: [
|
||||||
|
{
|
||||||
|
apiGroups: ["some-api-group"],
|
||||||
|
resources: ["some-kind"],
|
||||||
|
verbs: ["get"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
nonResourceRules: [],
|
||||||
|
},
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "resourceRules has no matching resource with list verb",
|
||||||
|
status: {
|
||||||
|
incomplete: false,
|
||||||
|
resourceRules: [
|
||||||
|
{
|
||||||
|
apiGroups: [""],
|
||||||
|
resources: ["services"],
|
||||||
|
verbs: ["list"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
nonResourceRules: [],
|
||||||
|
},
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
] as TestCase[]).forEach(({ description, status, expected }) => {
|
||||||
|
describe(`when api returns ${description}`, () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await createSelfSubjectRulesReviewMock.resolve({
|
||||||
|
body: {
|
||||||
|
status,
|
||||||
|
spec: {},
|
||||||
|
},
|
||||||
|
response: null as unknown as IncomingMessage,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
const permissionCheck = await requestPermissions("irrelevant-namespace");
|
it(`allows the request to complete, and 'canListResource' will return ${expected}`, async () => {
|
||||||
|
const canListResource = await request;
|
||||||
|
|
||||||
expect(permissionCheck({
|
expect(canListResource(someKubeResource)).toBe(expected);
|
||||||
apiName: "pods",
|
});
|
||||||
group: "",
|
});
|
||||||
kind: "Pod",
|
|
||||||
namespaced: true,
|
|
||||||
})).toBeTruthy();
|
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
describe("when first resourceRule has list permissions for everything", () => {
|
describe("when api rejects", () => {
|
||||||
it("return truthy function", async () => {
|
beforeEach(async () => {
|
||||||
const requestPermissions = requestNamespaceListPermissions(createStubProxyConfig(
|
await createSelfSubjectRulesReviewMock.reject(new Error("unknown error"));
|
||||||
new Promise((resolve) => resolve({
|
});
|
||||||
body: {
|
|
||||||
status: {
|
|
||||||
incomplete: false,
|
|
||||||
resourceRules: [
|
|
||||||
{
|
|
||||||
apiGroups: ["*"],
|
|
||||||
verbs: ["list"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
apiGroups: ["*"],
|
|
||||||
verbs: ["get"],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
nonResourceRules: [],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})),
|
|
||||||
) as any);
|
|
||||||
|
|
||||||
const permissionCheck = await requestPermissions("irrelevant-namespace");
|
it("allows the request to complete, and 'canListResource' will return true", async () => {
|
||||||
|
const canListResource = await request;
|
||||||
|
|
||||||
expect(permissionCheck({
|
expect(canListResource(someKubeResource)).toBe(true);
|
||||||
apiName: "pods",
|
});
|
||||||
group: "",
|
|
||||||
kind: "Pod",
|
|
||||||
namespaced: true,
|
|
||||||
})).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("when first resourceRule has list permissions for asked resource", () => {
|
|
||||||
it("return truthy function", async () => {
|
|
||||||
const requestPermissions = requestNamespaceListPermissions(createStubProxyConfig(
|
|
||||||
new Promise((resolve) => resolve({
|
|
||||||
body: {
|
|
||||||
status: {
|
|
||||||
incomplete: false,
|
|
||||||
resourceRules: [
|
|
||||||
{
|
|
||||||
apiGroups: [""],
|
|
||||||
resources: ["pods"],
|
|
||||||
verbs: ["list"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
apiGroups: ["*"],
|
|
||||||
verbs: ["get"],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
nonResourceRules: [],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})),
|
|
||||||
) as any);
|
|
||||||
|
|
||||||
const permissionCheck = await requestPermissions("irrelevant-namespace");
|
|
||||||
|
|
||||||
expect(permissionCheck({
|
|
||||||
apiName: "pods",
|
|
||||||
group: "",
|
|
||||||
kind: "Pod",
|
|
||||||
namespaced: true,
|
|
||||||
})).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("when last resourceRule has all permissions for everything", () => {
|
|
||||||
it("return truthy function", async () => {
|
|
||||||
const requestPermissions = requestNamespaceListPermissions(createStubProxyConfig(
|
|
||||||
new Promise((resolve) => resolve({
|
|
||||||
body: {
|
|
||||||
status: {
|
|
||||||
incomplete: false,
|
|
||||||
resourceRules: [
|
|
||||||
{
|
|
||||||
apiGroups: ["*"],
|
|
||||||
verbs: ["get"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
apiGroups: ["*"],
|
|
||||||
verbs: ["*"],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
nonResourceRules: [],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})),
|
|
||||||
) as any);
|
|
||||||
|
|
||||||
const permissionCheck = await requestPermissions("irrelevant-namespace");
|
|
||||||
|
|
||||||
expect(permissionCheck({
|
|
||||||
apiName: "pods",
|
|
||||||
group: "",
|
|
||||||
kind: "Pod",
|
|
||||||
namespaced: true,
|
|
||||||
})).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("when last resourceRule has list permissions for everything", () => {
|
|
||||||
it("return truthy function", async () => {
|
|
||||||
const requestPermissions = requestNamespaceListPermissions(createStubProxyConfig(
|
|
||||||
new Promise((resolve) => resolve({
|
|
||||||
body: {
|
|
||||||
status: {
|
|
||||||
incomplete: false,
|
|
||||||
resourceRules: [
|
|
||||||
{
|
|
||||||
apiGroups: ["*"],
|
|
||||||
verbs: ["get"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
apiGroups: ["*"],
|
|
||||||
verbs: ["list"],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
nonResourceRules: [],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})),
|
|
||||||
) as any);
|
|
||||||
|
|
||||||
const permissionCheck = await requestPermissions("irrelevant-namespace");
|
|
||||||
|
|
||||||
expect(permissionCheck({
|
|
||||||
apiName: "pods",
|
|
||||||
group: "",
|
|
||||||
kind: "Pod",
|
|
||||||
namespaced: true,
|
|
||||||
})).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("when last resourceRule has list permissions for asked resource", () => {
|
|
||||||
it("return truthy function", async () => {
|
|
||||||
const requestPermissions = requestNamespaceListPermissions(createStubProxyConfig(
|
|
||||||
new Promise((resolve) => resolve({
|
|
||||||
body: {
|
|
||||||
status: {
|
|
||||||
incomplete: false,
|
|
||||||
resourceRules: [
|
|
||||||
{
|
|
||||||
apiGroups: ["*"],
|
|
||||||
verbs: ["get"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
apiGroups: [""],
|
|
||||||
resources: ["pods"],
|
|
||||||
verbs: ["list"],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
nonResourceRules: [],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})),
|
|
||||||
) as any);
|
|
||||||
|
|
||||||
const permissionCheck = await requestPermissions("irrelevant-namespace");
|
|
||||||
|
|
||||||
expect(permissionCheck({
|
|
||||||
apiName: "pods",
|
|
||||||
group: "",
|
|
||||||
kind: "Pod",
|
|
||||||
namespaced: true,
|
|
||||||
})).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("when resourceRules has matching resource without list verb", () => {
|
|
||||||
it("return falsy function", async () => {
|
|
||||||
const requestPermissions = requestNamespaceListPermissions(createStubProxyConfig(
|
|
||||||
new Promise((resolve) => resolve({
|
|
||||||
body: {
|
|
||||||
status: {
|
|
||||||
incomplete: false,
|
|
||||||
resourceRules: [
|
|
||||||
{
|
|
||||||
apiGroups: [""],
|
|
||||||
resources: ["pods"],
|
|
||||||
verbs: ["get"],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
nonResourceRules: [],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})),
|
|
||||||
) as any);
|
|
||||||
|
|
||||||
const permissionCheck = await requestPermissions("irrelevant-namespace");
|
|
||||||
|
|
||||||
expect(permissionCheck({
|
|
||||||
apiName: "pods",
|
|
||||||
group: "",
|
|
||||||
kind: "Pod",
|
|
||||||
namespaced: true,
|
|
||||||
})).toBeFalsy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("when resourceRules has no matching resource with list verb", () => {
|
|
||||||
it("return falsy function", async () => {
|
|
||||||
const requestPermissions = requestNamespaceListPermissions(createStubProxyConfig(
|
|
||||||
new Promise((resolve) => resolve({
|
|
||||||
body: {
|
|
||||||
status: {
|
|
||||||
incomplete: false,
|
|
||||||
resourceRules: [
|
|
||||||
{
|
|
||||||
apiGroups: [""],
|
|
||||||
resources: ["services"],
|
|
||||||
verbs: ["list"],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
nonResourceRules: [],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})),
|
|
||||||
) as any);
|
|
||||||
|
|
||||||
const permissionCheck = await requestPermissions("irrelevant-namespace");
|
|
||||||
|
|
||||||
expect(permissionCheck({
|
|
||||||
apiName: "pods",
|
|
||||||
group: "",
|
|
||||||
kind: "Pod",
|
|
||||||
namespaced: true,
|
|
||||||
})).toBeFalsy();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const someKubeResource: KubeApiResource = {
|
||||||
|
apiName: "some-kind",
|
||||||
|
group: "some-api-group",
|
||||||
|
kind: "SomeKind",
|
||||||
|
namespaced: true,
|
||||||
|
};
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import type { ClusterId } from "../cluster-types";
|
import type { ClusterId } from "../cluster-types";
|
||||||
import type { MessageChannel } from "../utils/channel/message-channel-listener-injection-token";
|
import type { MessageChannel } from "@k8slens/messaging";
|
||||||
|
|
||||||
export const clusterVisibilityChannel: MessageChannel<ClusterId | null> = {
|
export const clusterVisibilityChannel: MessageChannel<ClusterId | null> = {
|
||||||
id: "cluster-visibility",
|
id: "cluster-visibility",
|
||||||
|
|||||||
@ -13,7 +13,7 @@ import userStoreInjectable from "../user-store/user-store.injectable";
|
|||||||
|
|
||||||
export type InitializeSentryReportingWith = (initSentry: (opts: BrowserOptions | ElectronMainOptions) => void) => void;
|
export type InitializeSentryReportingWith = (initSentry: (opts: BrowserOptions | ElectronMainOptions) => void) => void;
|
||||||
|
|
||||||
const mapProcessName = (type: "browser" | "renderer" | "worker") => type === "browser" ? "main" : type;
|
const mapProcessName = (type: "browser" | "renderer" | "worker" | "utility") => type === "browser" ? "main" : type;
|
||||||
|
|
||||||
const initializeSentryReportingWithInjectable = getInjectable({
|
const initializeSentryReportingWithInjectable = getInjectable({
|
||||||
id: "initialize-sentry-reporting-with",
|
id: "initialize-sentry-reporting-with",
|
||||||
|
|||||||
@ -3,13 +3,15 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { formatDuration } from "@k8slens/utilities";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an AbortController with an associated timeout
|
* Creates an AbortController with an associated timeout
|
||||||
* @param timeout The number of milliseconds before this controller will auto abort
|
* @param timeout The number of milliseconds before this controller will auto abort
|
||||||
*/
|
*/
|
||||||
export function withTimeout(timeout: number): AbortController {
|
export function withTimeout(timeout: number): AbortController {
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
const id = setTimeout(() => controller.abort(), timeout);
|
const id = setTimeout(() => controller.abort(`Operation timed out: timeout ${formatDuration(timeout)}`), timeout);
|
||||||
|
|
||||||
controller.signal.addEventListener("abort", () => clearTimeout(id));
|
controller.signal.addEventListener("abort", () => clearTimeout(id));
|
||||||
|
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { IpcRendererNavigationEvents } from "../ipc/navigation-events";
|
import { IpcRendererNavigationEvents } from "../ipc/navigation-events";
|
||||||
import type { MessageChannel } from "../utils/channel/message-channel-listener-injection-token";
|
import type { MessageChannel } from "@k8slens/messaging";
|
||||||
|
|
||||||
export type AppNavigationChannel = MessageChannel<string>;
|
export type AppNavigationChannel = MessageChannel<string>;
|
||||||
|
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { IpcRendererNavigationEvents } from "../ipc/navigation-events";
|
import { IpcRendererNavigationEvents } from "../ipc/navigation-events";
|
||||||
import type { MessageChannel } from "../utils/channel/message-channel-listener-injection-token";
|
import type { MessageChannel } from "@k8slens/messaging";
|
||||||
|
|
||||||
export type ClusterFrameNavigationChannel = MessageChannel<string>;
|
export type ClusterFrameNavigationChannel = MessageChannel<string>;
|
||||||
|
|
||||||
|
|||||||
@ -1,11 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { getGlobalOverride } from "@k8slens/test-utils";
|
|
||||||
import copyInjectable from "./copy.injectable";
|
|
||||||
|
|
||||||
export default getGlobalOverride(copyInjectable, () => async () => {
|
|
||||||
throw new Error("tried to copy filepaths without override");
|
|
||||||
});
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { getGlobalOverride } from "@k8slens/test-utils";
|
|
||||||
import lstatInjectable from "./lstat.injectable";
|
|
||||||
|
|
||||||
export default getGlobalOverride(lstatInjectable, () => async () => {
|
|
||||||
throw new Error("tried to lstat a filepath without override");
|
|
||||||
});
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { getGlobalOverride } from "@k8slens/test-utils";
|
|
||||||
import readDirectoryInjectable from "./read-directory.injectable";
|
|
||||||
|
|
||||||
export default getGlobalOverride(readDirectoryInjectable, () => async () => {
|
|
||||||
throw new Error("tried to read a directory's content without override");
|
|
||||||
});
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { getGlobalOverride } from "@k8slens/test-utils";
|
|
||||||
import removePathInjectable from "./remove.injectable";
|
|
||||||
|
|
||||||
export default getGlobalOverride(removePathInjectable, () => async () => {
|
|
||||||
throw new Error("tried to remove path without override");
|
|
||||||
});
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { getGlobalOverride } from "@k8slens/test-utils";
|
|
||||||
import writeFileInjectable from "./write-file.injectable";
|
|
||||||
|
|
||||||
export default getGlobalOverride(writeFileInjectable, () => async () => {
|
|
||||||
throw new Error("tried to write file without override");
|
|
||||||
});
|
|
||||||
@ -3,11 +3,10 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import type { HelmRepo } from "./helm-repo";
|
import type { HelmRepo } from "./helm-repo";
|
||||||
import type { AsyncResult } from "@k8slens/utilities";
|
import type { Result } from "@k8slens/utilities";
|
||||||
import type { RequestChannel } from "../utils/channel/request-channel-listener-injection-token";
|
import { getRequestChannel } from "@k8slens/messaging";
|
||||||
|
|
||||||
export type AddHelmRepositoryChannel = RequestChannel<HelmRepo, AsyncResult<void, string>>;
|
export const addHelmRepositoryChannel = getRequestChannel<
|
||||||
|
HelmRepo,
|
||||||
export const addHelmRepositoryChannel: AddHelmRepositoryChannel = {
|
Result<void, string>
|
||||||
id: "add-helm-repository-channel",
|
>("add-helm-repository-channel");
|
||||||
};
|
|
||||||
|
|||||||
@ -4,10 +4,9 @@
|
|||||||
*/
|
*/
|
||||||
import type { HelmRepo } from "./helm-repo";
|
import type { HelmRepo } from "./helm-repo";
|
||||||
import type { AsyncResult } from "@k8slens/utilities";
|
import type { AsyncResult } from "@k8slens/utilities";
|
||||||
import type { RequestChannel } from "../utils/channel/request-channel-listener-injection-token";
|
import { getRequestChannel } from "@k8slens/messaging";
|
||||||
|
|
||||||
export type GetActiveHelmRepositoriesChannel = RequestChannel<void, AsyncResult<HelmRepo[]>>;
|
export const getActiveHelmRepositoriesChannel = getRequestChannel<
|
||||||
|
void,
|
||||||
export const getActiveHelmRepositoriesChannel: GetActiveHelmRepositoriesChannel = {
|
AsyncResult<HelmRepo[]>
|
||||||
id: "get-helm-active-list-repositories",
|
>("get-helm-active-list-repositories");
|
||||||
};
|
|
||||||
|
|||||||
@ -3,11 +3,10 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import type { AsyncResult } from "@k8slens/utilities";
|
import type { AsyncResult } from "@k8slens/utilities";
|
||||||
import type { RequestChannel } from "../utils/channel/request-channel-listener-injection-token";
|
import { getRequestChannel } from "@k8slens/messaging";
|
||||||
import type { HelmRepo } from "./helm-repo";
|
import type { HelmRepo } from "./helm-repo";
|
||||||
|
|
||||||
export type RemoveHelmRepositoryChannel = RequestChannel<HelmRepo, AsyncResult<void, string>>;
|
export const removeHelmRepositoryChannel = getRequestChannel<
|
||||||
|
HelmRepo,
|
||||||
export const removeHelmRepositoryChannel: RemoveHelmRepositoryChannel = {
|
AsyncResult<void, string>
|
||||||
id: "remove-helm-repository-channel",
|
>("remove-helm-repository-channel");
|
||||||
};
|
|
||||||
|
|||||||
@ -14,7 +14,7 @@ import { hotbarStoreMigrationInjectionToken } from "./migrations-token";
|
|||||||
import getBasenameOfPathInjectable from "../path/get-basename.injectable";
|
import getBasenameOfPathInjectable from "../path/get-basename.injectable";
|
||||||
import { baseStoreIpcChannelPrefixesInjectionToken } from "../base-store/channel-prefix";
|
import { baseStoreIpcChannelPrefixesInjectionToken } from "../base-store/channel-prefix";
|
||||||
import { persistStateToConfigInjectionToken } from "../base-store/save-to-file";
|
import { persistStateToConfigInjectionToken } from "../base-store/save-to-file";
|
||||||
import { enlistMessageChannelListenerInjectionToken } from "../utils/channel/enlist-message-channel-listener-injection-token";
|
import { enlistMessageChannelListenerInjectionToken } from "@k8slens/messaging";
|
||||||
import { shouldBaseStoreDisableSyncInIpcListenerInjectionToken } from "../base-store/disable-sync";
|
import { shouldBaseStoreDisableSyncInIpcListenerInjectionToken } from "../base-store/disable-sync";
|
||||||
|
|
||||||
const hotbarStoreInjectable = getInjectable({
|
const hotbarStoreInjectable = getInjectable({
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import type { DiContainer } from "@ogre-tools/injectable";
|
import type { DiContainer } from "@ogre-tools/injectable";
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import clusterFrameContextForNamespacedResourcesInjectable from "../../../renderer/cluster-frame-context/for-namespaced-resources.injectable";
|
import clusterFrameContextForNamespacedResourcesInjectable from "../../../renderer/cluster-frame-context/for-namespaced-resources.injectable";
|
||||||
import hostedClusterInjectable from "../../../renderer/cluster-frame-context/hosted-cluster.injectable";
|
import hostedClusterInjectable from "../../../renderer/cluster-frame-context/hosted-cluster.injectable";
|
||||||
import { getDiForUnitTesting } from "../../../renderer/getDiForUnitTesting";
|
import { getDiForUnitTesting } from "../../../renderer/getDiForUnitTesting";
|
||||||
@ -21,6 +22,9 @@ import maybeKubeApiInjectable from "../maybe-kube-api.injectable";
|
|||||||
// eslint-disable-next-line no-restricted-imports
|
// eslint-disable-next-line no-restricted-imports
|
||||||
import { KubeApi as ExternalKubeApi } from "../../../extensions/common-api/k8s-api";
|
import { KubeApi as ExternalKubeApi } from "../../../extensions/common-api/k8s-api";
|
||||||
import { Cluster } from "../../cluster/cluster";
|
import { Cluster } from "../../cluster/cluster";
|
||||||
|
import { runInAction } from "mobx";
|
||||||
|
import { customResourceDefinitionApiInjectionToken } from "../api-manager/crd-api-token";
|
||||||
|
import assert from "assert";
|
||||||
|
|
||||||
class TestApi extends KubeApi<KubeObject> {
|
class TestApi extends KubeApi<KubeObject> {
|
||||||
protected async checkPreferredVersion() {
|
protected async checkPreferredVersion() {
|
||||||
@ -117,4 +121,90 @@ describe("ApiManager", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("given than a CRD has a default KubeApi registered for it", () => {
|
||||||
|
const apiBase = "/apis/aquasecurity.github.io/v1alpha1/vulnerabilityreports";
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
runInAction(() => {
|
||||||
|
di.register(getInjectable({
|
||||||
|
id: `default-kube-api-for-custom-resource-definition-${apiBase}`,
|
||||||
|
instantiate: (di) => {
|
||||||
|
const objectConstructor = class extends KubeObject {
|
||||||
|
static readonly kind = "VulnerabilityReport";
|
||||||
|
static readonly namespaced = true;
|
||||||
|
static readonly apiBase = apiBase;
|
||||||
|
};
|
||||||
|
|
||||||
|
return Object.assign(
|
||||||
|
new KubeApi({
|
||||||
|
logger: di.inject(loggerInjectable),
|
||||||
|
maybeKubeApi: di.inject(maybeKubeApiInjectable),
|
||||||
|
}, { objectConstructor }),
|
||||||
|
{
|
||||||
|
myField: 1,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
injectionToken: customResourceDefinitionApiInjectionToken,
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("can be retrieved from apiManager", () => {
|
||||||
|
expect(apiManager.getApi(apiBase)).toMatchObject({
|
||||||
|
myField: 1,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("can have a default KubeObjectStore instance retrieved for it", () => {
|
||||||
|
expect(apiManager.getStore(apiBase)).toBeInstanceOf(KubeObjectStore);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("given that an extension registers an api with the same apibase", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
void Object.assign(new ExternalKubeApi({
|
||||||
|
objectConstructor: KubeObject,
|
||||||
|
apiBase,
|
||||||
|
kind: "VulnerabilityReport",
|
||||||
|
}), {
|
||||||
|
myField: 2,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("the extension's instance is retrievable instead from apiManager", () => {
|
||||||
|
expect(apiManager.getApi(apiBase)).toMatchObject({
|
||||||
|
myField: 2,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("can have a default KubeObjectStore instance retrieved for it", () => {
|
||||||
|
expect(apiManager.getStore(apiBase)).toBeInstanceOf(KubeObjectStore);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("given that an extension registers a store for the same apibase", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
const api = apiManager.getApi(apiBase);
|
||||||
|
|
||||||
|
assert(api);
|
||||||
|
|
||||||
|
apiManager.registerStore(Object.assign(
|
||||||
|
new KubeObjectStore({
|
||||||
|
context: di.inject(clusterFrameContextForNamespacedResourcesInjectable),
|
||||||
|
logger: di.inject(loggerInjectable),
|
||||||
|
}, api),
|
||||||
|
{
|
||||||
|
someField: 2,
|
||||||
|
},
|
||||||
|
));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("can gets the custom KubeObjectStore instance instead", () => {
|
||||||
|
expect(apiManager.getStore(apiBase)).toMatchObject({
|
||||||
|
someField: 2,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -22,7 +22,6 @@ import { flushPromises } from "@k8slens/test-utils";
|
|||||||
import createKubeJsonApiInjectable from "../create-kube-json-api.injectable";
|
import createKubeJsonApiInjectable from "../create-kube-json-api.injectable";
|
||||||
import type { IKubeWatchEvent } from "../kube-watch-event";
|
import type { IKubeWatchEvent } from "../kube-watch-event";
|
||||||
import type { KubeJsonApiDataFor, KubeStatusData } from "../kube-object";
|
import type { KubeJsonApiDataFor, KubeStatusData } from "../kube-object";
|
||||||
import AbortController from "abort-controller";
|
|
||||||
import setupAutoRegistrationInjectable
|
import setupAutoRegistrationInjectable
|
||||||
from "../../../renderer/before-frame-starts/runnables/setup-auto-registration.injectable";
|
from "../../../renderer/before-frame-starts/runnables/setup-auto-registration.injectable";
|
||||||
import {
|
import {
|
||||||
|
|||||||
@ -10,7 +10,8 @@ import { autorun, action, observable } from "mobx";
|
|||||||
import type { KubeApi } from "../kube-api";
|
import type { KubeApi } from "../kube-api";
|
||||||
import type { KubeObject, ObjectReference } from "../kube-object";
|
import type { KubeObject, ObjectReference } from "../kube-object";
|
||||||
import { parseKubeApi, createKubeApiURL } from "../kube-api-parse";
|
import { parseKubeApi, createKubeApiURL } from "../kube-api-parse";
|
||||||
import { iter } from "@k8slens/utilities";
|
import { getOrInsertWith, iter } from "@k8slens/utilities";
|
||||||
|
import type { CreateCustomResourceStore } from "./create-custom-resource-store.injectable";
|
||||||
|
|
||||||
export type RegisterableStore<Store> = Store extends KubeObjectStore<any, any, any>
|
export type RegisterableStore<Store> = Store extends KubeObjectStore<any, any, any>
|
||||||
? Store
|
? Store
|
||||||
@ -26,13 +27,15 @@ export type FindApiCallback = (api: KubeApi<KubeObject>) => boolean;
|
|||||||
|
|
||||||
interface Dependencies {
|
interface Dependencies {
|
||||||
readonly apis: IComputedValue<KubeApi[]>;
|
readonly apis: IComputedValue<KubeApi[]>;
|
||||||
|
readonly crdApis: IComputedValue<KubeApi[]>;
|
||||||
readonly stores: IComputedValue<KubeObjectStore[]>;
|
readonly stores: IComputedValue<KubeObjectStore[]>;
|
||||||
|
createCustomResourceStore: CreateCustomResourceStore;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ApiManager {
|
export class ApiManager {
|
||||||
private readonly externalApis = observable.array<KubeApi>();
|
private readonly externalApis = observable.array<KubeApi>();
|
||||||
private readonly externalStores = observable.array<KubeObjectStore>();
|
private readonly externalStores = observable.array<KubeObjectStore>();
|
||||||
|
private readonly defaultCrdStores = observable.map<string, KubeObjectStore>();
|
||||||
private readonly apis = observable.map<string, KubeApi>();
|
private readonly apis = observable.map<string, KubeApi>();
|
||||||
|
|
||||||
constructor(private readonly dependencies: Dependencies) {
|
constructor(private readonly dependencies: Dependencies) {
|
||||||
@ -56,6 +59,12 @@ export class ApiManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const crdApi of this.dependencies.crdApis.get()) {
|
||||||
|
if (!newState.has(crdApi.apiBase)) {
|
||||||
|
newState.set(crdApi.apiBase, crdApi);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.apis.replace(newState);
|
this.apis.replace(newState);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -110,6 +119,16 @@ export class ApiManager {
|
|||||||
this.externalStores.push(store);
|
this.externalStores.push(store);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private apiIsDefaultCrdApi(api: KubeApi): boolean {
|
||||||
|
for (const crdApi of this.dependencies.crdApis.get()) {
|
||||||
|
if (crdApi.apiBase === api.apiBase) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
getStore(api: string | undefined): KubeObjectStore | undefined;
|
getStore(api: string | undefined): KubeObjectStore | undefined;
|
||||||
getStore<Api>(api: RegisterableApi<Api>): KubeObjectStoreFrom<Api> | undefined;
|
getStore<Api>(api: RegisterableApi<Api>): KubeObjectStoreFrom<Api> | undefined;
|
||||||
/**
|
/**
|
||||||
@ -130,9 +149,19 @@ export class ApiManager {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
return iter.chain(this.dependencies.stores.get().values())
|
const defaultResult = iter.chain(this.dependencies.stores.get().values())
|
||||||
.concat(this.externalStores.values())
|
.concat(this.externalStores.values())
|
||||||
.find(store => store.api.apiBase === api.apiBase);
|
.find(store => store.api.apiBase === api.apiBase);
|
||||||
|
|
||||||
|
if (defaultResult) {
|
||||||
|
return defaultResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.apiIsDefaultCrdApi(api)) {
|
||||||
|
return getOrInsertWith(this.defaultCrdStores, api.apiBase, () => this.dependencies.createCustomResourceStore(api));
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
lookupApiLink(ref: ObjectReference, parentObject?: KubeObject): string {
|
lookupApiLink(ref: ObjectReference, parentObject?: KubeObject): string {
|
||||||
|
|||||||
@ -5,11 +5,9 @@
|
|||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import EventEmitter from "events";
|
import EventEmitter from "events";
|
||||||
import type TypedEventEmitter from "typed-emitter";
|
import type TypedEventEmitter from "typed-emitter";
|
||||||
import type { CustomResourceDefinition } from "../endpoints";
|
|
||||||
import type { KubeApi } from "../kube-api";
|
import type { KubeApi } from "../kube-api";
|
||||||
|
|
||||||
export interface LegacyAutoRegistration {
|
export interface LegacyAutoRegistration {
|
||||||
customResourceDefinition: (crd: CustomResourceDefinition) => void;
|
|
||||||
kubeApi: (api: KubeApi<any, any>) => void;
|
kubeApi: (api: KubeApi<any, any>) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,11 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { getInjectionToken } from "@ogre-tools/injectable";
|
||||||
|
import type { KubeApi } from "../kube-api";
|
||||||
|
|
||||||
|
export const customResourceDefinitionApiInjectionToken = getInjectionToken<KubeApi>({
|
||||||
|
id: "custom-resource-definition-api-token",
|
||||||
|
});
|
||||||
@ -0,0 +1,27 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
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 { KubeObjectStoreDependencies } from "../kube-object.store";
|
||||||
|
import { CustomResourceStore } from "./resource.store";
|
||||||
|
|
||||||
|
export type CreateCustomResourceStore = <K extends KubeObject>(api: KubeApi<K>) => CustomResourceStore<K>;
|
||||||
|
|
||||||
|
const createCustomResourceStoreInjectable = getInjectable({
|
||||||
|
id: "create-custom-resource-store",
|
||||||
|
instantiate: (di): CreateCustomResourceStore => {
|
||||||
|
const deps: KubeObjectStoreDependencies = {
|
||||||
|
context: di.inject(clusterFrameContextForNamespacedResourcesInjectable),
|
||||||
|
logger: di.inject(loggerInjectable),
|
||||||
|
};
|
||||||
|
|
||||||
|
return (api) => new CustomResourceStore(deps, api);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default createCustomResourceStoreInjectable;
|
||||||
@ -9,6 +9,8 @@ import { computedInjectManyInjectable } from "@ogre-tools/injectable-extension-f
|
|||||||
import { kubeObjectStoreInjectionToken } from "./kube-object-store-token";
|
import { kubeObjectStoreInjectionToken } from "./kube-object-store-token";
|
||||||
import { kubeApiInjectionToken } from "../kube-api/kube-api-injection-token";
|
import { kubeApiInjectionToken } from "../kube-api/kube-api-injection-token";
|
||||||
import { computed } from "mobx";
|
import { computed } from "mobx";
|
||||||
|
import { customResourceDefinitionApiInjectionToken } from "./crd-api-token";
|
||||||
|
import createCustomResourceStoreInjectable from "./create-custom-resource-store.injectable";
|
||||||
|
|
||||||
const apiManagerInjectable = getInjectable({
|
const apiManagerInjectable = getInjectable({
|
||||||
id: "api-manager",
|
id: "api-manager",
|
||||||
@ -23,6 +25,10 @@ const apiManagerInjectable = getInjectable({
|
|||||||
stores: storesAndApisCanBeCreated
|
stores: storesAndApisCanBeCreated
|
||||||
? computedInjectMany(kubeObjectStoreInjectionToken)
|
? computedInjectMany(kubeObjectStoreInjectionToken)
|
||||||
: computed(() => []),
|
: computed(() => []),
|
||||||
|
crdApis: storesAndApisCanBeCreated
|
||||||
|
? computedInjectMany(customResourceDefinitionApiInjectionToken)
|
||||||
|
: computed(() => []),
|
||||||
|
createCustomResourceStore: di.inject(createCustomResourceStoreInjectable),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -20,7 +20,6 @@ import type { Patch } from "rfc6902";
|
|||||||
import assert from "assert";
|
import assert from "assert";
|
||||||
import type { PartialDeep } from "type-fest";
|
import type { PartialDeep } from "type-fest";
|
||||||
import type { Logger } from "../logger";
|
import type { Logger } from "../logger";
|
||||||
import type AbortController from "abort-controller";
|
|
||||||
import { matches } from "lodash/fp";
|
import { matches } from "lodash/fp";
|
||||||
import { makeObservable, observable } from "mobx";
|
import { makeObservable, observable } from "mobx";
|
||||||
|
|
||||||
|
|||||||
@ -17,7 +17,6 @@ import type { Patch } from "rfc6902";
|
|||||||
import type { Logger } from "../logger";
|
import type { Logger } from "../logger";
|
||||||
import assert from "assert";
|
import assert from "assert";
|
||||||
import type { PartialDeep } from "type-fest";
|
import type { PartialDeep } from "type-fest";
|
||||||
import AbortController from "abort-controller";
|
|
||||||
import type { ClusterContext } from "../../renderer/cluster-frame-context/cluster-frame-context";
|
import type { ClusterContext } from "../../renderer/cluster-frame-context/cluster-frame-context";
|
||||||
import autoBind from "auto-bind";
|
import autoBind from "auto-bind";
|
||||||
|
|
||||||
@ -89,7 +88,7 @@ export interface KubeObjectStoreDependencies {
|
|||||||
readonly logger: Logger;
|
readonly logger: Logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
export abstract class KubeObjectStore<
|
export class KubeObjectStore<
|
||||||
K extends KubeObject = KubeObject,
|
K extends KubeObject = KubeObject,
|
||||||
A extends KubeApi<K, D> = KubeApi<K, KubeJsonApiDataFor<K>>,
|
A extends KubeApi<K, D> = KubeApi<K, KubeJsonApiDataFor<K>>,
|
||||||
D extends KubeJsonApiDataFor<K> = KubeApiDataFrom<K, A>,
|
D extends KubeJsonApiDataFor<K> = KubeApiDataFrom<K, A>,
|
||||||
|
|||||||
@ -4,11 +4,9 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { getInjectionToken } from "@ogre-tools/injectable";
|
import { getInjectionToken } from "@ogre-tools/injectable";
|
||||||
import type { Asyncify } from "type-fest";
|
|
||||||
import type { RequestChannelHandler } from "../../main/utils/channel/channel-listeners/listener-tokens";
|
|
||||||
import type { ClusterId } from "../cluster-types";
|
import type { ClusterId } from "../cluster-types";
|
||||||
import type { AsyncResult } from "@k8slens/utilities";
|
import type { AsyncResult, Result } from "@k8slens/utilities";
|
||||||
import type { RequestChannel } from "../utils/channel/request-channel-listener-injection-token";
|
import { getRequestChannel } from "@k8slens/messaging";
|
||||||
|
|
||||||
export interface KubectlApplyAllArgs {
|
export interface KubectlApplyAllArgs {
|
||||||
clusterId: ClusterId;
|
clusterId: ClusterId;
|
||||||
@ -16,11 +14,12 @@ export interface KubectlApplyAllArgs {
|
|||||||
extraArgs: string[];
|
extraArgs: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const kubectlApplyAllChannel: RequestChannel<KubectlApplyAllArgs, AsyncResult<string, string>> = {
|
export const kubectlApplyAllChannel = getRequestChannel<
|
||||||
id: "kubectl-apply-all",
|
KubectlApplyAllArgs,
|
||||||
};
|
Result<string, string>
|
||||||
|
>("kubectl-apply-all");
|
||||||
|
|
||||||
export type KubectlApplyAll = Asyncify<RequestChannelHandler<typeof kubectlApplyAllChannel>>;
|
export type KubectlApplyAll = (req: KubectlApplyAllArgs) => AsyncResult<string, string>;
|
||||||
|
|
||||||
export const kubectlApplyAllInjectionToken = getInjectionToken<KubectlApplyAll>({
|
export const kubectlApplyAllInjectionToken = getInjectionToken<KubectlApplyAll>({
|
||||||
id: "kubectl-apply-all",
|
id: "kubectl-apply-all",
|
||||||
@ -32,11 +31,12 @@ export interface KubectlDeleteAllArgs {
|
|||||||
extraArgs: string[];
|
extraArgs: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const kubectlDeleteAllChannel: RequestChannel<KubectlDeleteAllArgs, AsyncResult<string, string>> = {
|
export const kubectlDeleteAllChannel = getRequestChannel<
|
||||||
id: "kubectl-delete-all",
|
KubectlDeleteAllArgs,
|
||||||
};
|
Result<string, string>
|
||||||
|
>("kubectl-delete-all");
|
||||||
|
|
||||||
export type KubectlDeleteAll = Asyncify<RequestChannelHandler<typeof kubectlDeleteAllChannel>>;
|
export type KubectlDeleteAll = (req: KubectlDeleteAllArgs) => AsyncResult<string, string>;
|
||||||
|
|
||||||
export const kubectlDeleteAllInjectionToken = getInjectionToken<KubectlDeleteAll>({
|
export const kubectlDeleteAllInjectionToken = getInjectionToken<KubectlDeleteAll>({
|
||||||
id: "kubectl-delete-all",
|
id: "kubectl-delete-all",
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import type { MessageChannel } from "../utils/channel/message-channel-listener-injection-token";
|
import type { MessageChannel } from "@k8slens/messaging";
|
||||||
|
|
||||||
export type RootFrameHasRenderedChannel = MessageChannel<void>;
|
export type RootFrameHasRenderedChannel = MessageChannel<void>;
|
||||||
|
|
||||||
|
|||||||
@ -16,7 +16,7 @@ import { baseStoreIpcChannelPrefixesInjectionToken } from "../base-store/channel
|
|||||||
import { shouldBaseStoreDisableSyncInIpcListenerInjectionToken } from "../base-store/disable-sync";
|
import { shouldBaseStoreDisableSyncInIpcListenerInjectionToken } from "../base-store/disable-sync";
|
||||||
import { persistStateToConfigInjectionToken } from "../base-store/save-to-file";
|
import { persistStateToConfigInjectionToken } from "../base-store/save-to-file";
|
||||||
import getBasenameOfPathInjectable from "../path/get-basename.injectable";
|
import getBasenameOfPathInjectable from "../path/get-basename.injectable";
|
||||||
import { enlistMessageChannelListenerInjectionToken } from "../utils/channel/enlist-message-channel-listener-injection-token";
|
import { enlistMessageChannelListenerInjectionToken } from "@k8slens/messaging";
|
||||||
import userStorePreferenceDescriptorsInjectable from "./preference-descriptors.injectable";
|
import userStorePreferenceDescriptorsInjectable from "./preference-descriptors.injectable";
|
||||||
|
|
||||||
const userStoreInjectable = getInjectable({
|
const userStoreInjectable = getInjectable({
|
||||||
|
|||||||
@ -1,245 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
import type { DiContainer } from "@ogre-tools/injectable";
|
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
|
||||||
import type { SendMessageToChannel } from "./message-to-channel-injection-token";
|
|
||||||
import { sendMessageToChannelInjectionToken } from "./message-to-channel-injection-token";
|
|
||||||
import type { ApplicationBuilder } from "../../../renderer/components/test-utils/get-application-builder";
|
|
||||||
import { getApplicationBuilder } from "../../../renderer/components/test-utils/get-application-builder";
|
|
||||||
import type { LensWindow } from "../../../main/start-main-application/lens-window/application-window/create-lens-window.injectable";
|
|
||||||
import type { MessageChannel } from "./message-channel-listener-injection-token";
|
|
||||||
import { messageChannelListenerInjectionToken } from "./message-channel-listener-injection-token";
|
|
||||||
import type { RequestFromChannel } from "./request-from-channel-injection-token";
|
|
||||||
import { requestFromChannelInjectionToken } from "./request-from-channel-injection-token";
|
|
||||||
import type { RequestChannel } from "./request-channel-listener-injection-token";
|
|
||||||
import type { AsyncFnMock } from "@async-fn/jest";
|
|
||||||
import asyncFn from "@async-fn/jest";
|
|
||||||
import { getPromiseStatus } from "@k8slens/test-utils";
|
|
||||||
import { runInAction } from "mobx";
|
|
||||||
import type { RequestChannelHandler } from "../../../main/utils/channel/channel-listeners/listener-tokens";
|
|
||||||
import {
|
|
||||||
getRequestChannelListenerInjectable,
|
|
||||||
requestChannelListenerInjectionToken,
|
|
||||||
} from "../../../main/utils/channel/channel-listeners/listener-tokens";
|
|
||||||
|
|
||||||
type TestMessageChannel = MessageChannel<string>;
|
|
||||||
type TestRequestChannel = RequestChannel<string, string>;
|
|
||||||
|
|
||||||
describe("channel", () => {
|
|
||||||
describe("messaging from main to renderer, given listener for channel in a window and application has started", () => {
|
|
||||||
let messageListenerInWindowMock: jest.Mock;
|
|
||||||
let mainDi: DiContainer;
|
|
||||||
let messageToChannel: SendMessageToChannel;
|
|
||||||
let builder: ApplicationBuilder;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
builder = getApplicationBuilder();
|
|
||||||
|
|
||||||
messageListenerInWindowMock = jest.fn();
|
|
||||||
|
|
||||||
const testChannelListenerInTestWindowInjectable = getInjectable({
|
|
||||||
id: "test-channel-listener-in-test-window",
|
|
||||||
|
|
||||||
instantiate: () => ({
|
|
||||||
channel: testMessageChannel,
|
|
||||||
handler: messageListenerInWindowMock,
|
|
||||||
}),
|
|
||||||
|
|
||||||
injectionToken: messageChannelListenerInjectionToken,
|
|
||||||
});
|
|
||||||
|
|
||||||
builder.beforeWindowStart(({ windowDi }) => {
|
|
||||||
runInAction(() => {
|
|
||||||
windowDi.register(testChannelListenerInTestWindowInjectable);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
mainDi = builder.mainDi;
|
|
||||||
|
|
||||||
await builder.startHidden();
|
|
||||||
|
|
||||||
messageToChannel = mainDi.inject(sendMessageToChannelInjectionToken);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("given window is started", () => {
|
|
||||||
let someWindowFake: LensWindow;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
someWindowFake = builder.applicationWindow.create("some-window");
|
|
||||||
|
|
||||||
await someWindowFake.start();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("when sending message, triggers listener in window", () => {
|
|
||||||
messageToChannel(testMessageChannel, "some-message");
|
|
||||||
|
|
||||||
expect(messageListenerInWindowMock).toHaveBeenCalledWith("some-message");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("given window is hidden, when sending message, does not trigger listener in window", () => {
|
|
||||||
someWindowFake.close();
|
|
||||||
|
|
||||||
messageToChannel(testMessageChannel, "some-message");
|
|
||||||
|
|
||||||
expect(messageListenerInWindowMock).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("given multiple started windows, when sending message, triggers listeners in all windows", async () => {
|
|
||||||
const someWindowFake = builder.applicationWindow.create("some-window");
|
|
||||||
const someOtherWindowFake = builder.applicationWindow.create("some-other-window");
|
|
||||||
|
|
||||||
await someWindowFake.start();
|
|
||||||
await someOtherWindowFake.start();
|
|
||||||
|
|
||||||
messageToChannel(testMessageChannel, "some-message");
|
|
||||||
|
|
||||||
expect(messageListenerInWindowMock.mock.calls).toEqual([
|
|
||||||
["some-message"],
|
|
||||||
["some-message"],
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("messaging from renderer to main, given listener for channel in a main and application has started", () => {
|
|
||||||
let messageListenerInMainMock: jest.Mock;
|
|
||||||
let messageToChannel: SendMessageToChannel;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
const applicationBuilder = getApplicationBuilder();
|
|
||||||
|
|
||||||
messageListenerInMainMock = jest.fn();
|
|
||||||
|
|
||||||
const testChannelListenerInMainInjectable = getInjectable({
|
|
||||||
id: "test-channel-listener-in-main",
|
|
||||||
|
|
||||||
instantiate: () => ({
|
|
||||||
channel: testMessageChannel,
|
|
||||||
handler: messageListenerInMainMock,
|
|
||||||
}),
|
|
||||||
|
|
||||||
injectionToken: messageChannelListenerInjectionToken,
|
|
||||||
});
|
|
||||||
|
|
||||||
applicationBuilder.beforeApplicationStart(({ mainDi }) => {
|
|
||||||
runInAction(() => {
|
|
||||||
mainDi.register(testChannelListenerInMainInjectable);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
await applicationBuilder.render();
|
|
||||||
|
|
||||||
const windowDi = applicationBuilder.applicationWindow.only.di;
|
|
||||||
|
|
||||||
messageToChannel = windowDi.inject(sendMessageToChannelInjectionToken);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("when sending message, triggers listener in main", () => {
|
|
||||||
messageToChannel(testMessageChannel, "some-message");
|
|
||||||
|
|
||||||
expect(messageListenerInMainMock).toHaveBeenCalledWith("some-message");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("requesting from main in renderer, given listener for channel in a main and application has started", () => {
|
|
||||||
let requestListenerInMainMock: AsyncFnMock<RequestChannelHandler<TestRequestChannel>>;
|
|
||||||
let requestFromChannel: RequestFromChannel;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
const applicationBuilder = getApplicationBuilder();
|
|
||||||
|
|
||||||
requestListenerInMainMock = asyncFn();
|
|
||||||
|
|
||||||
const testChannelListenerInMainInjectable = getRequestChannelListenerInjectable({
|
|
||||||
channel: testRequestChannel,
|
|
||||||
handler: () => requestListenerInMainMock,
|
|
||||||
});
|
|
||||||
|
|
||||||
applicationBuilder.beforeApplicationStart(({ mainDi }) => {
|
|
||||||
runInAction(() => {
|
|
||||||
mainDi.register(testChannelListenerInMainInjectable);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
await applicationBuilder.render();
|
|
||||||
|
|
||||||
const windowDi = applicationBuilder.applicationWindow.only.di;
|
|
||||||
|
|
||||||
requestFromChannel = windowDi.inject(
|
|
||||||
requestFromChannelInjectionToken,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("when requesting from channel", () => {
|
|
||||||
let actualPromise: Promise<string>;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
actualPromise = requestFromChannel(testRequestChannel, "some-request");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("triggers listener in main", () => {
|
|
||||||
expect(requestListenerInMainMock).toHaveBeenCalledWith("some-request");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("does not resolve yet", async () => {
|
|
||||||
const promiseStatus = await getPromiseStatus(actualPromise);
|
|
||||||
|
|
||||||
expect(promiseStatus.fulfilled).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("when main resolves with response, resolves with response", async () => {
|
|
||||||
await requestListenerInMainMock.resolve("some-response");
|
|
||||||
|
|
||||||
const actual = await actualPromise;
|
|
||||||
|
|
||||||
expect(actual).toBe("some-response");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("when registering multiple handlers for the same channel, throws", async () => {
|
|
||||||
const applicationBuilder = getApplicationBuilder();
|
|
||||||
|
|
||||||
const someChannelListenerInjectable = getInjectable({
|
|
||||||
id: "some-channel-listener",
|
|
||||||
|
|
||||||
instantiate: () => ({
|
|
||||||
channel: testRequestChannel,
|
|
||||||
handler: () => () => "irrelevant",
|
|
||||||
}),
|
|
||||||
|
|
||||||
injectionToken: requestChannelListenerInjectionToken,
|
|
||||||
});
|
|
||||||
|
|
||||||
const someOtherChannelListenerInjectable = getInjectable({
|
|
||||||
id: "some-other-channel-listener",
|
|
||||||
|
|
||||||
instantiate: () => ({
|
|
||||||
channel: testRequestChannel,
|
|
||||||
handler: () => () => "irrelevant",
|
|
||||||
}),
|
|
||||||
|
|
||||||
injectionToken: requestChannelListenerInjectionToken,
|
|
||||||
});
|
|
||||||
|
|
||||||
applicationBuilder.beforeApplicationStart(({ mainDi }) => {
|
|
||||||
runInAction(() => {
|
|
||||||
mainDi.register(someChannelListenerInjectable);
|
|
||||||
mainDi.register(someOtherChannelListenerInjectable);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
await expect(applicationBuilder.render()).rejects.toThrow('Tried to register a multiple channel handlers for "some-request-channel-id", only one handler is supported for a request channel.');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const testMessageChannel: TestMessageChannel = {
|
|
||||||
id: "some-message-channel-id",
|
|
||||||
};
|
|
||||||
|
|
||||||
const testRequestChannel: TestRequestChannel = {
|
|
||||||
id: "some-request-channel-id",
|
|
||||||
};
|
|
||||||
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
import { getInjectionToken } from "@ogre-tools/injectable";
|
|
||||||
import type { Disposer } from "@k8slens/utilities";
|
|
||||||
import type { MessageChannel, MessageChannelListener } from "./message-channel-listener-injection-token";
|
|
||||||
|
|
||||||
export type EnlistMessageChannelListener = (listener: MessageChannelListener<MessageChannel<unknown>>) => Disposer;
|
|
||||||
|
|
||||||
export const enlistMessageChannelListenerInjectionToken = getInjectionToken<EnlistMessageChannelListener>({
|
|
||||||
id: "enlist-message-channel-listener",
|
|
||||||
});
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
import type { RequestChannel } from "./request-channel-listener-injection-token";
|
|
||||||
|
|
||||||
export const getRequestChannel = <Request, Response>(id: string): RequestChannel<Request, Response> => ({
|
|
||||||
id,
|
|
||||||
});
|
|
||||||
@ -1,25 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
|
||||||
import { getStartableStoppable } from "../get-startable-stoppable";
|
|
||||||
import { messageChannelListenerInjectionToken } from "./message-channel-listener-injection-token";
|
|
||||||
import { enlistMessageChannelListenerInjectionToken } from "./enlist-message-channel-listener-injection-token";
|
|
||||||
import { disposer } from "@k8slens/utilities";
|
|
||||||
|
|
||||||
const listeningOnMessageChannelsInjectable = getInjectable({
|
|
||||||
id: "listening-on-message-channels",
|
|
||||||
|
|
||||||
instantiate: (di) => {
|
|
||||||
const enlistMessageChannelListener = di.inject(enlistMessageChannelListenerInjectionToken);
|
|
||||||
const messageChannelListeners = di.injectMany(messageChannelListenerInjectionToken);
|
|
||||||
|
|
||||||
return getStartableStoppable("listening-on-channels", () => (
|
|
||||||
disposer(messageChannelListeners.map(enlistMessageChannelListener))
|
|
||||||
));
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
export default listeningOnMessageChannelsInjectable;
|
|
||||||
@ -1,51 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
import type { DiContainerForInjection } from "@ogre-tools/injectable";
|
|
||||||
import { getInjectable, getInjectionToken } from "@ogre-tools/injectable";
|
|
||||||
|
|
||||||
export interface MessageChannel<Message> {
|
|
||||||
id: string;
|
|
||||||
_messageSignature?: Message; // only used to mark `Message` as used
|
|
||||||
}
|
|
||||||
|
|
||||||
export type MessageChannelHandler<Channel> = Channel extends MessageChannel<infer Message>
|
|
||||||
? (message: Message) => void
|
|
||||||
: never;
|
|
||||||
|
|
||||||
export interface MessageChannelListener<Channel> {
|
|
||||||
channel: Channel;
|
|
||||||
handler: MessageChannelHandler<Channel>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const messageChannelListenerInjectionToken = getInjectionToken<MessageChannelListener<MessageChannel<unknown>>>(
|
|
||||||
{
|
|
||||||
id: "message-channel-listener",
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
export interface GetMessageChannelListenerInfo<
|
|
||||||
Channel extends MessageChannel<Message>,
|
|
||||||
Message,
|
|
||||||
> {
|
|
||||||
id: string;
|
|
||||||
channel: Channel;
|
|
||||||
handler: (di: DiContainerForInjection) => MessageChannelHandler<Channel>;
|
|
||||||
causesSideEffects?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getMessageChannelListenerInjectable<
|
|
||||||
Channel extends MessageChannel<Message>,
|
|
||||||
Message,
|
|
||||||
>(info: GetMessageChannelListenerInfo<Channel, Message>) {
|
|
||||||
return getInjectable({
|
|
||||||
id: `${info.channel.id}-listener-${info.id}`,
|
|
||||||
instantiate: (di) => ({
|
|
||||||
channel: info.channel,
|
|
||||||
handler: info.handler(di),
|
|
||||||
}),
|
|
||||||
injectionToken: messageChannelListenerInjectionToken,
|
|
||||||
causesSideEffects: info.causesSideEffects,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
|
|
||||||
export interface RequestChannel<Request, Response> {
|
|
||||||
id: string;
|
|
||||||
_requestSignature?: Request; // used only to mark `Request` as "used"
|
|
||||||
_responseSignature?: Response; // used only to mark `Response` as "used"
|
|
||||||
}
|
|
||||||
25
packages/core/src/common/utils/registrator-helper.ts
Normal file
25
packages/core/src/common/utils/registrator-helper.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { iter } from "@k8slens/utilities";
|
||||||
|
import type { DiContainerForInjection, Injectable } from "@ogre-tools/injectable";
|
||||||
|
|
||||||
|
// Register new injectables and deregister removed injectables by id
|
||||||
|
|
||||||
|
export const injectableDifferencingRegistratorWith = (di: DiContainerForInjection) => (
|
||||||
|
(rawCurrent: Injectable<any, any, any>[], rawPrevious: Injectable<any, any, any>[] = []) => {
|
||||||
|
const current = new Map(rawCurrent.map(inj => [inj.id, inj]));
|
||||||
|
const previous = new Map(rawPrevious.map(inj => [inj.id, inj]));
|
||||||
|
const toAdd = iter.chain(current.entries())
|
||||||
|
.filter(([id]) => !previous.has(id))
|
||||||
|
.collect(entries => new Map(entries));
|
||||||
|
const toRemove = iter.chain(previous.entries())
|
||||||
|
.filter(([id]) => !current.has(id))
|
||||||
|
.collect(entries => new Map(entries));
|
||||||
|
|
||||||
|
di.deregister(...toRemove.values());
|
||||||
|
di.register(...toAdd.values());
|
||||||
|
}
|
||||||
|
);
|
||||||
@ -2,10 +2,8 @@
|
|||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import type { RequestChannel } from "../channel/request-channel-listener-injection-token";
|
import { getRequestChannel } from "@k8slens/messaging";
|
||||||
|
|
||||||
export type ResolveSystemProxyChannel = RequestChannel<string, string>;
|
export const resolveSystemProxyChannel = getRequestChannel<string, string>(
|
||||||
|
"resolve-system-proxy-channel",
|
||||||
export const resolveSystemProxyChannel: ResolveSystemProxyChannel = {
|
);
|
||||||
id: "resolve-system-proxy-channel",
|
|
||||||
};
|
|
||||||
|
|||||||
@ -3,13 +3,13 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { syncBoxChannel } from "./channels";
|
import { syncBoxChannel } from "./channels";
|
||||||
import { getMessageChannelListenerInjectable } from "../channel/message-channel-listener-injection-token";
|
import { getMessageChannelListenerInjectable } from "@k8slens/messaging";
|
||||||
import syncBoxStateInjectable from "./sync-box-state.injectable";
|
import syncBoxStateInjectable from "./sync-box-state.injectable";
|
||||||
|
|
||||||
const syncBoxChannelListenerInjectable = getMessageChannelListenerInjectable({
|
const syncBoxChannelListenerInjectable = getMessageChannelListenerInjectable({
|
||||||
id: "init",
|
id: "init",
|
||||||
channel: syncBoxChannel,
|
channel: syncBoxChannel,
|
||||||
handler: (di) => ({ id, value }) => di.inject(syncBoxStateInjectable, id).set(value),
|
getHandler: (di) => ({ id, value }) => di.inject(syncBoxStateInjectable, id).set(value),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default syncBoxChannelListenerInjectable;
|
export default syncBoxChannelListenerInjectable;
|
||||||
|
|||||||
@ -2,20 +2,12 @@
|
|||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import type { MessageChannel } from "../channel/message-channel-listener-injection-token";
|
import { getMessageChannel, getRequestChannel } from "@k8slens/messaging";
|
||||||
import type { RequestChannel } from "../channel/request-channel-listener-injection-token";
|
|
||||||
|
|
||||||
export type SyncBoxChannel = MessageChannel<{ id: string; value: any }>;
|
export const syncBoxChannel =
|
||||||
|
getMessageChannel<{ id: string; value: any }>("sync-box-channel");
|
||||||
|
|
||||||
export const syncBoxChannel: SyncBoxChannel = {
|
export const syncBoxInitialValueChannel = getRequestChannel<
|
||||||
id: "sync-box-channel",
|
|
||||||
};
|
|
||||||
|
|
||||||
export type SyncBoxInitialValueChannel = RequestChannel<
|
|
||||||
void,
|
void,
|
||||||
{ id: string; value: any }[]
|
{ id: string; value: any }[]
|
||||||
>;
|
>("sync-box-initial-value-channel");
|
||||||
|
|
||||||
export const syncBoxInitialValueChannel: SyncBoxInitialValueChannel = {
|
|
||||||
id: "sync-box-initial-value-channel",
|
|
||||||
};
|
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable";
|
|||||||
import type { IObservableValue } from "mobx";
|
import type { IObservableValue } from "mobx";
|
||||||
import { computed } from "mobx";
|
import { computed } from "mobx";
|
||||||
import { syncBoxChannel } from "./channels";
|
import { syncBoxChannel } from "./channels";
|
||||||
import { sendMessageToChannelInjectionToken } from "../channel/message-to-channel-injection-token";
|
import { sendMessageToChannelInjectionToken } from "@k8slens/messaging";
|
||||||
import syncBoxStateInjectable from "./sync-box-state.injectable";
|
import syncBoxStateInjectable from "./sync-box-state.injectable";
|
||||||
import type { SyncBox } from "./sync-box-injection-token";
|
import type { SyncBox } from "./sync-box-injection-token";
|
||||||
import { toJS } from "../toJS";
|
import { toJS } from "../toJS";
|
||||||
|
|||||||
@ -1,19 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
|
||||||
import type { MessageChannelHandler } from "../channel/message-channel-listener-injection-token";
|
|
||||||
import type { SyncBoxChannel } from "./channels";
|
|
||||||
import syncBoxStateInjectable from "./sync-box-state.injectable";
|
|
||||||
|
|
||||||
const syncBoxChannelHandlerInjectable = getInjectable({
|
|
||||||
id: "sync-box-channel-handler",
|
|
||||||
instantiate: (di): MessageChannelHandler<SyncBoxChannel> => {
|
|
||||||
const getSyncBoxState = (id: string) => di.inject(syncBoxStateInjectable, id);
|
|
||||||
|
|
||||||
return ({ id, value }) => getSyncBoxState(id)?.set(value);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default syncBoxChannelHandlerInjectable;
|
|
||||||
@ -7,7 +7,7 @@ import { getInjectionToken } from "@ogre-tools/injectable";
|
|||||||
import { SemVer } from "semver";
|
import { SemVer } from "semver";
|
||||||
import type { InitializableState } from "../initializable-state/create";
|
import type { InitializableState } from "../initializable-state/create";
|
||||||
import { createInitializableState } from "../initializable-state/create";
|
import { createInitializableState } from "../initializable-state/create";
|
||||||
import type { RequestChannel } from "../utils/channel/request-channel-listener-injection-token";
|
import type { RequestChannel } from "@k8slens/messaging";
|
||||||
|
|
||||||
export const buildVersionInjectionToken = getInjectionToken<InitializableState<string>>({
|
export const buildVersionInjectionToken = getInjectionToken<InitializableState<string>>({
|
||||||
id: "build-version-token",
|
id: "build-version-token",
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import { persistStateToConfigInjectionToken } from "../base-store/save-to-file";
|
|||||||
import getConfigurationFileModelInjectable from "../get-configuration-file-model/get-configuration-file-model.injectable";
|
import getConfigurationFileModelInjectable from "../get-configuration-file-model/get-configuration-file-model.injectable";
|
||||||
import loggerInjectable from "../logger.injectable";
|
import loggerInjectable from "../logger.injectable";
|
||||||
import getBasenameOfPathInjectable from "../path/get-basename.injectable";
|
import getBasenameOfPathInjectable from "../path/get-basename.injectable";
|
||||||
import { enlistMessageChannelListenerInjectionToken } from "../utils/channel/enlist-message-channel-listener-injection-token";
|
import { enlistMessageChannelListenerInjectionToken } from "@k8slens/messaging";
|
||||||
import storeMigrationVersionInjectable from "../vars/store-migration-version.injectable";
|
import storeMigrationVersionInjectable from "../vars/store-migration-version.injectable";
|
||||||
import { weblinkStoreMigrationInjectionToken } from "./migration-token";
|
import { weblinkStoreMigrationInjectionToken } from "./migration-token";
|
||||||
import { WeblinkStore } from "./weblink-store";
|
import { WeblinkStore } from "./weblink-store";
|
||||||
|
|||||||
@ -2,29 +2,18 @@
|
|||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import type { Injectable } from "@ogre-tools/injectable";
|
|
||||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
import { difference, find, map } from "lodash";
|
|
||||||
import { reaction, runInAction } from "mobx";
|
import { reaction, runInAction } from "mobx";
|
||||||
import { disposer } from "@k8slens/utilities";
|
import { disposer } from "@k8slens/utilities";
|
||||||
import type { LensExtension } from "../../lens-extension";
|
import type { LensExtension } from "../../lens-extension";
|
||||||
import { extensionRegistratorInjectionToken } from "../extension-registrator-injection-token";
|
import { extensionRegistratorInjectionToken } from "../extension-registrator-injection-token";
|
||||||
|
import { injectableDifferencingRegistratorWith } from "../../../common/utils/registrator-helper";
|
||||||
|
|
||||||
export interface Extension {
|
export interface Extension {
|
||||||
register: () => void;
|
register: () => void;
|
||||||
deregister: () => void;
|
deregister: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const idsToInjectables = (ids: string[], injectables: Injectable<any, any, any>[]) => ids.map(id => {
|
|
||||||
const injectable = find(injectables, { id });
|
|
||||||
|
|
||||||
if (!injectable) {
|
|
||||||
throw new Error(`Injectable ${id} not found`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return injectable;
|
|
||||||
});
|
|
||||||
|
|
||||||
const extensionInjectable = getInjectable({
|
const extensionInjectable = getInjectable({
|
||||||
id: "extension",
|
id: "extension",
|
||||||
|
|
||||||
@ -35,36 +24,27 @@ const extensionInjectable = getInjectable({
|
|||||||
instantiate: (childDi) => {
|
instantiate: (childDi) => {
|
||||||
const extensionRegistrators = childDi.injectMany(extensionRegistratorInjectionToken);
|
const extensionRegistrators = childDi.injectMany(extensionRegistratorInjectionToken);
|
||||||
const reactionDisposer = disposer();
|
const reactionDisposer = disposer();
|
||||||
|
const injectableDifferencingRegistrator = injectableDifferencingRegistratorWith(childDi);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
register: () => {
|
register: () => {
|
||||||
extensionRegistrators.forEach((getInjectablesOfExtension) => {
|
for (const extensionRegistrator of extensionRegistrators) {
|
||||||
const injectables = getInjectablesOfExtension(instance);
|
const injectables = extensionRegistrator(instance);
|
||||||
|
|
||||||
reactionDisposer.push(
|
if (Array.isArray(injectables)) {
|
||||||
// injectables is either an array or a computed array, in which case
|
runInAction(() => {
|
||||||
// we need to update the registered injectables with a reaction every time they change
|
injectableDifferencingRegistrator(injectables);
|
||||||
reaction(
|
});
|
||||||
() => Array.isArray(injectables) ? injectables : injectables.get(),
|
} else {
|
||||||
(currentInjectables, previousInjectables = []) => {
|
reactionDisposer.push(reaction(
|
||||||
// Register new injectables and deregister removed injectables by id
|
() => injectables.get(),
|
||||||
const currentIds = map(currentInjectables, "id");
|
injectableDifferencingRegistrator,
|
||||||
const previousIds = map(previousInjectables, "id");
|
{
|
||||||
const idsToAdd = difference(currentIds, previousIds);
|
|
||||||
const idsToRemove = previousIds.filter(previousId => !currentIds.includes(previousId));
|
|
||||||
|
|
||||||
if (idsToRemove.length > 0) {
|
|
||||||
childDi.deregister(...idsToInjectables(idsToRemove, previousInjectables));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (idsToAdd.length > 0) {
|
|
||||||
childDi.register(...idsToInjectables(idsToAdd, currentInjectables));
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
fireImmediately: true,
|
fireImmediately: true,
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
});
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
deregister: () => {
|
deregister: () => {
|
||||||
|
|||||||
@ -12,7 +12,7 @@ import { baseStoreIpcChannelPrefixesInjectionToken } from "../../../common/base-
|
|||||||
import { shouldBaseStoreDisableSyncInIpcListenerInjectionToken } from "../../../common/base-store/disable-sync";
|
import { shouldBaseStoreDisableSyncInIpcListenerInjectionToken } from "../../../common/base-store/disable-sync";
|
||||||
import { persistStateToConfigInjectionToken } from "../../../common/base-store/save-to-file";
|
import { persistStateToConfigInjectionToken } from "../../../common/base-store/save-to-file";
|
||||||
import getBasenameOfPathInjectable from "../../../common/path/get-basename.injectable";
|
import getBasenameOfPathInjectable from "../../../common/path/get-basename.injectable";
|
||||||
import { enlistMessageChannelListenerInjectionToken } from "../../../common/utils/channel/enlist-message-channel-listener-injection-token";
|
import { enlistMessageChannelListenerInjectionToken } from "@k8slens/messaging";
|
||||||
import ensureHashedDirectoryForExtensionInjectable from "./ensure-hashed-directory-for-extension.injectable";
|
import ensureHashedDirectoryForExtensionInjectable from "./ensure-hashed-directory-for-extension.injectable";
|
||||||
import { registeredExtensionsInjectable } from "./registered-extensions.injectable";
|
import { registeredExtensionsInjectable } from "./registered-extensions.injectable";
|
||||||
|
|
||||||
|
|||||||
@ -20,7 +20,7 @@ import { baseStoreIpcChannelPrefixesInjectionToken } from "../common/base-store/
|
|||||||
import { shouldBaseStoreDisableSyncInIpcListenerInjectionToken } from "../common/base-store/disable-sync";
|
import { shouldBaseStoreDisableSyncInIpcListenerInjectionToken } from "../common/base-store/disable-sync";
|
||||||
import { persistStateToConfigInjectionToken } from "../common/base-store/save-to-file";
|
import { persistStateToConfigInjectionToken } from "../common/base-store/save-to-file";
|
||||||
import getBasenameOfPathInjectable from "../common/path/get-basename.injectable";
|
import getBasenameOfPathInjectable from "../common/path/get-basename.injectable";
|
||||||
import { enlistMessageChannelListenerInjectionToken } from "../common/utils/channel/enlist-message-channel-listener-injection-token";
|
import { enlistMessageChannelListenerInjectionToken } from "@k8slens/messaging";
|
||||||
|
|
||||||
export interface ExtensionStoreParams<T extends object> extends BaseStoreParams<T> {
|
export interface ExtensionStoreParams<T extends object> extends BaseStoreParams<T> {
|
||||||
migrations?: Migrations<T>;
|
migrations?: Migrations<T>;
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import { persistStateToConfigInjectionToken } from "../../common/base-store/save
|
|||||||
import getConfigurationFileModelInjectable from "../../common/get-configuration-file-model/get-configuration-file-model.injectable";
|
import getConfigurationFileModelInjectable from "../../common/get-configuration-file-model/get-configuration-file-model.injectable";
|
||||||
import loggerInjectable from "../../common/logger.injectable";
|
import loggerInjectable from "../../common/logger.injectable";
|
||||||
import getBasenameOfPathInjectable from "../../common/path/get-basename.injectable";
|
import getBasenameOfPathInjectable from "../../common/path/get-basename.injectable";
|
||||||
import { enlistMessageChannelListenerInjectionToken } from "../../common/utils/channel/enlist-message-channel-listener-injection-token";
|
import { enlistMessageChannelListenerInjectionToken } from "@k8slens/messaging";
|
||||||
import storeMigrationVersionInjectable from "../../common/vars/store-migration-version.injectable";
|
import storeMigrationVersionInjectable from "../../common/vars/store-migration-version.injectable";
|
||||||
import { ExtensionsStore } from "./extensions-store";
|
import { ExtensionsStore } from "./extensions-store";
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { autorun } from "mobx";
|
import { autorun } from "mobx";
|
||||||
import { getStartableStoppable } from "../../../common/utils/get-startable-stoppable";
|
import { getStartableStoppable } from "@k8slens/startable-stoppable";
|
||||||
import populateApplicationMenuInjectable from "./populate-application-menu.injectable";
|
import populateApplicationMenuInjectable from "./populate-application-menu.injectable";
|
||||||
import applicationMenuItemCompositeInjectable from "./application-menu-item-composite.injectable";
|
import applicationMenuItemCompositeInjectable from "./application-menu-item-composite.injectable";
|
||||||
|
|
||||||
|
|||||||
@ -28,7 +28,7 @@ const checkForUpdatesMenuItemInjectable = getInjectable({
|
|||||||
id: "check-for-updates",
|
id: "check-for-updates",
|
||||||
parentId: isMac ? "mac" : "help",
|
parentId: isMac ? "mac" : "help",
|
||||||
orderNumber: isMac ? 20 : 50,
|
orderNumber: isMac ? 20 : 50,
|
||||||
label: "Check for updates",
|
label: "Check for Updates...",
|
||||||
isShown: updatingIsEnabled,
|
isShown: updatingIsEnabled,
|
||||||
|
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
|
|||||||
@ -146,7 +146,7 @@ describe("installing update using tray", () => {
|
|||||||
it("name of tray item for checking updates indicates that checking is happening", () => {
|
it("name of tray item for checking updates indicates that checking is happening", () => {
|
||||||
expect(
|
expect(
|
||||||
builder.tray.get("check-for-updates")?.label,
|
builder.tray.get("check-for-updates")?.label,
|
||||||
).toBe("Checking for updates...");
|
).toBe("Checking for Updates...");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("user cannot install update yet", () => {
|
it("user cannot install update yet", () => {
|
||||||
@ -177,7 +177,7 @@ describe("installing update using tray", () => {
|
|||||||
it("name of tray item for checking updates no longer indicates that checking is happening", () => {
|
it("name of tray item for checking updates no longer indicates that checking is happening", () => {
|
||||||
expect(
|
expect(
|
||||||
builder.tray.get("check-for-updates")?.label,
|
builder.tray.get("check-for-updates")?.label,
|
||||||
).toBe("Check for updates");
|
).toBe("Check for Updates...");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders", () => {
|
it("renders", () => {
|
||||||
@ -241,7 +241,7 @@ describe("installing update using tray", () => {
|
|||||||
it("name of tray item for checking updates no longer indicates that downloading is happening", () => {
|
it("name of tray item for checking updates no longer indicates that downloading is happening", () => {
|
||||||
expect(
|
expect(
|
||||||
builder.tray.get("check-for-updates")?.label,
|
builder.tray.get("check-for-updates")?.label,
|
||||||
).toBe("Check for updates");
|
).toBe("Check for Updates...");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders", () => {
|
it("renders", () => {
|
||||||
@ -269,7 +269,7 @@ describe("installing update using tray", () => {
|
|||||||
it("name of tray item for checking updates no longer indicates that downloading is happening", () => {
|
it("name of tray item for checking updates no longer indicates that downloading is happening", () => {
|
||||||
expect(
|
expect(
|
||||||
builder.tray.get("check-for-updates")?.label,
|
builder.tray.get("check-for-updates")?.label,
|
||||||
).toBe("Check for updates");
|
).toBe("Check for Updates...");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders", () => {
|
it("renders", () => {
|
||||||
|
|||||||
@ -47,10 +47,10 @@ const checkForUpdatesTrayItemInjectable = getInjectable({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (checkingForUpdatesState.value.get()) {
|
if (checkingForUpdatesState.value.get()) {
|
||||||
return "Checking for updates...";
|
return "Checking for Updates...";
|
||||||
}
|
}
|
||||||
|
|
||||||
return "Check for updates";
|
return "Check for Updates...";
|
||||||
}),
|
}),
|
||||||
|
|
||||||
enabled: computed(() => !checkingForUpdatesState.value.get() && !downloadingUpdateState.value.get()),
|
enabled: computed(() => !checkingForUpdatesState.value.get() && !downloadingUpdateState.value.get()),
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { getStartableStoppable } from "../../../../../common/utils/get-startable-stoppable";
|
import { getStartableStoppable } from "@k8slens/startable-stoppable";
|
||||||
import processCheckingForUpdatesInjectable from "../../../main/process-checking-for-updates.injectable";
|
import processCheckingForUpdatesInjectable from "../../../main/process-checking-for-updates.injectable";
|
||||||
import withOrphanPromiseInjectable from "../../../../../common/utils/with-orphan-promise/with-orphan-promise.injectable";
|
import withOrphanPromiseInjectable from "../../../../../common/utils/with-orphan-promise/with-orphan-promise.injectable";
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import type { MessageChannel } from "../../../common/utils/channel/message-channel-listener-injection-token";
|
import type { MessageChannel } from "@k8slens/messaging";
|
||||||
|
|
||||||
export type RestartAndInstallUpdateChannel = MessageChannel<void>;
|
export type RestartAndInstallUpdateChannel = MessageChannel<void>;
|
||||||
|
|
||||||
|
|||||||
@ -3,13 +3,13 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { restartAndInstallUpdateChannel } from "../../common/restart-and-install-update-channel";
|
import { restartAndInstallUpdateChannel } from "../../common/restart-and-install-update-channel";
|
||||||
import { getMessageChannelListenerInjectable } from "../../../../common/utils/channel/message-channel-listener-injection-token";
|
import { getMessageChannelListenerInjectable } from "@k8slens/messaging";
|
||||||
import quitAndInstallUpdateInjectable from "../quit-and-install-update.injectable";
|
import quitAndInstallUpdateInjectable from "../quit-and-install-update.injectable";
|
||||||
|
|
||||||
const restartAndInstallUpdateListenerInjectable = getMessageChannelListenerInjectable({
|
const restartAndInstallUpdateListenerInjectable = getMessageChannelListenerInjectable({
|
||||||
id: "restart",
|
id: "restart",
|
||||||
channel: restartAndInstallUpdateChannel,
|
channel: restartAndInstallUpdateChannel,
|
||||||
handler: (di) => di.inject(quitAndInstallUpdateInjectable),
|
getHandler: (di) => di.inject(quitAndInstallUpdateInjectable),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default restartAndInstallUpdateListenerInjectable;
|
export default restartAndInstallUpdateListenerInjectable;
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { autorun } from "mobx";
|
import { autorun } from "mobx";
|
||||||
import { getStartableStoppable } from "../../../../common/utils/get-startable-stoppable";
|
import { getStartableStoppable } from "@k8slens/startable-stoppable";
|
||||||
import setUpdateOnQuitInjectable from "../../../../main/electron-app/features/set-update-on-quit.injectable";
|
import setUpdateOnQuitInjectable from "../../../../main/electron-app/features/set-update-on-quit.injectable";
|
||||||
import selectedUpdateChannelInjectable from "../../common/selected-update-channel/selected-update-channel.injectable";
|
import selectedUpdateChannelInjectable from "../../common/selected-update-channel/selected-update-channel.injectable";
|
||||||
import type { ReleaseChannel, UpdateChannel } from "../../common/update-channels";
|
import type { ReleaseChannel, UpdateChannel } from "../../common/update-channels";
|
||||||
|
|||||||
@ -4,13 +4,13 @@
|
|||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { restartAndInstallUpdateChannel } from "../common/restart-and-install-update-channel";
|
import { restartAndInstallUpdateChannel } from "../common/restart-and-install-update-channel";
|
||||||
import messageToChannelInjectable from "../../../renderer/utils/channel/message-to-channel.injectable";
|
import { sendMessageToChannelInjectionToken } from "@k8slens/messaging";
|
||||||
|
|
||||||
const restartAndInstallUpdateInjectable = getInjectable({
|
const restartAndInstallUpdateInjectable = getInjectable({
|
||||||
id: "restart-and-install-update",
|
id: "restart-and-install-update",
|
||||||
|
|
||||||
instantiate: (di) => {
|
instantiate: (di) => {
|
||||||
const messageToChannel = di.inject(messageToChannelInjectable);
|
const messageToChannel = di.inject(sendMessageToChannelInjectionToken);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
messageToChannel(restartAndInstallUpdateChannel);
|
messageToChannel(restartAndInstallUpdateChannel);
|
||||||
|
|||||||
@ -490,10 +490,10 @@ exports[`entity running technical tests when navigated to catalog renders 1`] =
|
|||||||
style="position: relative; height: 420000px; width: 100%; overflow: auto; will-change: transform; direction: ltr;"
|
style="position: relative; height: 420000px; width: 100%; overflow: auto; will-change: transform; direction: ltr;"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
style="height: 33px; width: 100%;"
|
style="height: 36px; width: 100%;"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
style="position: absolute; left: 0px; top: 0px; height: 33px; width: 100%;"
|
style="position: absolute; left: 0px; top: 0px; height: 36px; width: 100%;"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="TableRow nowrap"
|
class="TableRow nowrap"
|
||||||
@ -1206,10 +1206,10 @@ exports[`entity running technical tests when navigated to catalog when details p
|
|||||||
style="position: relative; height: 420000px; width: 100%; overflow: auto; will-change: transform; direction: ltr;"
|
style="position: relative; height: 420000px; width: 100%; overflow: auto; will-change: transform; direction: ltr;"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
style="height: 33px; width: 100%;"
|
style="height: 36px; width: 100%;"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
style="position: absolute; left: 0px; top: 0px; height: 33px; width: 100%;"
|
style="position: absolute; left: 0px; top: 0px; height: 36px; width: 100%;"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="TableRow nowrap"
|
class="TableRow nowrap"
|
||||||
|
|||||||
@ -739,10 +739,10 @@ exports[`opening catalog entity details panel when navigated to the catalog rend
|
|||||||
style="position: relative; height: 420000px; width: 100%; overflow: auto; will-change: transform; direction: ltr;"
|
style="position: relative; height: 420000px; width: 100%; overflow: auto; will-change: transform; direction: ltr;"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
style="height: 99px; width: 100%;"
|
style="height: 108px; width: 100%;"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
style="position: absolute; left: 0px; top: 0px; height: 33px; width: 100%;"
|
style="position: absolute; left: 0px; top: 0px; height: 36px; width: 100%;"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="TableRow nowrap"
|
class="TableRow nowrap"
|
||||||
@ -815,7 +815,7 @@ exports[`opening catalog entity details panel when navigated to the catalog rend
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
style="position: absolute; left: 0px; top: 33px; height: 33px; width: 100%;"
|
style="position: absolute; left: 0px; top: 36px; height: 36px; width: 100%;"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="TableRow nowrap"
|
class="TableRow nowrap"
|
||||||
@ -888,7 +888,7 @@ exports[`opening catalog entity details panel when navigated to the catalog rend
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
style="position: absolute; left: 0px; top: 66px; height: 33px; width: 100%;"
|
style="position: absolute; left: 0px; top: 72px; height: 36px; width: 100%;"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="TableRow nowrap"
|
class="TableRow nowrap"
|
||||||
@ -1560,10 +1560,10 @@ exports[`opening catalog entity details panel when navigated to the catalog when
|
|||||||
style="position: relative; height: 420000px; width: 100%; overflow: auto; will-change: transform; direction: ltr;"
|
style="position: relative; height: 420000px; width: 100%; overflow: auto; will-change: transform; direction: ltr;"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
style="height: 99px; width: 100%;"
|
style="height: 108px; width: 100%;"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
style="position: absolute; left: 0px; top: 0px; height: 33px; width: 100%;"
|
style="position: absolute; left: 0px; top: 0px; height: 36px; width: 100%;"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="TableRow nowrap"
|
class="TableRow nowrap"
|
||||||
@ -1636,7 +1636,7 @@ exports[`opening catalog entity details panel when navigated to the catalog when
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
style="position: absolute; left: 0px; top: 33px; height: 33px; width: 100%;"
|
style="position: absolute; left: 0px; top: 36px; height: 36px; width: 100%;"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="TableRow nowrap"
|
class="TableRow nowrap"
|
||||||
@ -1709,7 +1709,7 @@ exports[`opening catalog entity details panel when navigated to the catalog when
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
style="position: absolute; left: 0px; top: 66px; height: 33px; width: 100%;"
|
style="position: absolute; left: 0px; top: 72px; height: 36px; width: 100%;"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="TableRow nowrap"
|
class="TableRow nowrap"
|
||||||
@ -2413,10 +2413,10 @@ exports[`opening catalog entity details panel when navigated to the catalog when
|
|||||||
style="position: relative; height: 420000px; width: 100%; overflow: auto; will-change: transform; direction: ltr;"
|
style="position: relative; height: 420000px; width: 100%; overflow: auto; will-change: transform; direction: ltr;"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
style="height: 99px; width: 100%;"
|
style="height: 108px; width: 100%;"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
style="position: absolute; left: 0px; top: 0px; height: 33px; width: 100%;"
|
style="position: absolute; left: 0px; top: 0px; height: 36px; width: 100%;"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="TableRow nowrap"
|
class="TableRow nowrap"
|
||||||
@ -2489,7 +2489,7 @@ exports[`opening catalog entity details panel when navigated to the catalog when
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
style="position: absolute; left: 0px; top: 33px; height: 33px; width: 100%;"
|
style="position: absolute; left: 0px; top: 36px; height: 36px; width: 100%;"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="TableRow nowrap"
|
class="TableRow nowrap"
|
||||||
@ -2562,7 +2562,7 @@ exports[`opening catalog entity details panel when navigated to the catalog when
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
style="position: absolute; left: 0px; top: 66px; height: 33px; width: 100%;"
|
style="position: absolute; left: 0px; top: 72px; height: 36px; width: 100%;"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="TableRow nowrap"
|
class="TableRow nowrap"
|
||||||
@ -3266,10 +3266,10 @@ exports[`opening catalog entity details panel when navigated to the catalog when
|
|||||||
style="position: relative; height: 420000px; width: 100%; overflow: auto; will-change: transform; direction: ltr;"
|
style="position: relative; height: 420000px; width: 100%; overflow: auto; will-change: transform; direction: ltr;"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
style="height: 99px; width: 100%;"
|
style="height: 108px; width: 100%;"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
style="position: absolute; left: 0px; top: 0px; height: 33px; width: 100%;"
|
style="position: absolute; left: 0px; top: 0px; height: 36px; width: 100%;"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="TableRow nowrap"
|
class="TableRow nowrap"
|
||||||
@ -3342,7 +3342,7 @@ exports[`opening catalog entity details panel when navigated to the catalog when
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
style="position: absolute; left: 0px; top: 33px; height: 33px; width: 100%;"
|
style="position: absolute; left: 0px; top: 36px; height: 36px; width: 100%;"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="TableRow nowrap"
|
class="TableRow nowrap"
|
||||||
@ -3415,7 +3415,7 @@ exports[`opening catalog entity details panel when navigated to the catalog when
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
style="position: absolute; left: 0px; top: 66px; height: 33px; width: 100%;"
|
style="position: absolute; left: 0px; top: 72px; height: 36px; width: 100%;"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="TableRow nowrap"
|
class="TableRow nowrap"
|
||||||
@ -4370,10 +4370,10 @@ exports[`opening catalog entity details panel when navigated to the catalog when
|
|||||||
style="position: relative; height: 420000px; width: 100%; overflow: auto; will-change: transform; direction: ltr;"
|
style="position: relative; height: 420000px; width: 100%; overflow: auto; will-change: transform; direction: ltr;"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
style="height: 99px; width: 100%;"
|
style="height: 108px; width: 100%;"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
style="position: absolute; left: 0px; top: 0px; height: 33px; width: 100%;"
|
style="position: absolute; left: 0px; top: 0px; height: 36px; width: 100%;"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="TableRow nowrap"
|
class="TableRow nowrap"
|
||||||
@ -4446,7 +4446,7 @@ exports[`opening catalog entity details panel when navigated to the catalog when
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
style="position: absolute; left: 0px; top: 33px; height: 33px; width: 100%;"
|
style="position: absolute; left: 0px; top: 36px; height: 36px; width: 100%;"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="TableRow nowrap"
|
class="TableRow nowrap"
|
||||||
@ -4519,7 +4519,7 @@ exports[`opening catalog entity details panel when navigated to the catalog when
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
style="position: absolute; left: 0px; top: 66px; height: 33px; width: 100%;"
|
style="position: absolute; left: 0px; top: 72px; height: 36px; width: 100%;"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="TableRow nowrap"
|
class="TableRow nowrap"
|
||||||
@ -5211,10 +5211,10 @@ exports[`opening catalog entity details panel when navigated to the catalog when
|
|||||||
style="position: relative; height: 420000px; width: 100%; overflow: auto; will-change: transform; direction: ltr;"
|
style="position: relative; height: 420000px; width: 100%; overflow: auto; will-change: transform; direction: ltr;"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
style="height: 99px; width: 100%;"
|
style="height: 108px; width: 100%;"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
style="position: absolute; left: 0px; top: 0px; height: 33px; width: 100%;"
|
style="position: absolute; left: 0px; top: 0px; height: 36px; width: 100%;"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="TableRow nowrap"
|
class="TableRow nowrap"
|
||||||
@ -5287,7 +5287,7 @@ exports[`opening catalog entity details panel when navigated to the catalog when
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
style="position: absolute; left: 0px; top: 33px; height: 33px; width: 100%;"
|
style="position: absolute; left: 0px; top: 36px; height: 36px; width: 100%;"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="TableRow nowrap"
|
class="TableRow nowrap"
|
||||||
@ -5360,7 +5360,7 @@ exports[`opening catalog entity details panel when navigated to the catalog when
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
style="position: absolute; left: 0px; top: 66px; height: 33px; width: 100%;"
|
style="position: absolute; left: 0px; top: 72px; height: 36px; width: 100%;"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="TableRow nowrap"
|
class="TableRow nowrap"
|
||||||
@ -6052,10 +6052,10 @@ exports[`opening catalog entity details panel when navigated to the catalog when
|
|||||||
style="position: relative; height: 420000px; width: 100%; overflow: auto; will-change: transform; direction: ltr;"
|
style="position: relative; height: 420000px; width: 100%; overflow: auto; will-change: transform; direction: ltr;"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
style="height: 99px; width: 100%;"
|
style="height: 108px; width: 100%;"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
style="position: absolute; left: 0px; top: 0px; height: 33px; width: 100%;"
|
style="position: absolute; left: 0px; top: 0px; height: 36px; width: 100%;"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="TableRow nowrap"
|
class="TableRow nowrap"
|
||||||
@ -6128,7 +6128,7 @@ exports[`opening catalog entity details panel when navigated to the catalog when
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
style="position: absolute; left: 0px; top: 33px; height: 33px; width: 100%;"
|
style="position: absolute; left: 0px; top: 36px; height: 36px; width: 100%;"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="TableRow nowrap"
|
class="TableRow nowrap"
|
||||||
@ -6201,7 +6201,7 @@ exports[`opening catalog entity details panel when navigated to the catalog when
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
style="position: absolute; left: 0px; top: 66px; height: 33px; width: 100%;"
|
style="position: absolute; left: 0px; top: 72px; height: 36px; width: 100%;"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="TableRow nowrap"
|
class="TableRow nowrap"
|
||||||
|
|||||||
@ -2,7 +2,6 @@
|
|||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
|
import { getRequestChannel } from "@k8slens/messaging";
|
||||||
import { getRequestChannel } from "../../../common/utils/channel/get-request-channel";
|
|
||||||
|
|
||||||
export const casChannel = getRequestChannel<void, string[]>("certificate-authorities");
|
export const casChannel = getRequestChannel<void, string[]>("certificate-authorities");
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import { casChannel } from "../common/channel";
|
|||||||
import certificateAuthoritiesChannelListenerInjectable from "./channel-handler.injectable";
|
import certificateAuthoritiesChannelListenerInjectable from "./channel-handler.injectable";
|
||||||
|
|
||||||
export default getGlobalOverride(certificateAuthoritiesChannelListenerInjectable, () => ({
|
export default getGlobalOverride(certificateAuthoritiesChannelListenerInjectable, () => ({
|
||||||
|
id: "certificate-authorities-channel-listener",
|
||||||
channel: casChannel,
|
channel: casChannel,
|
||||||
handler: () => [],
|
handler: () => [],
|
||||||
}));
|
}));
|
||||||
|
|||||||
@ -2,14 +2,15 @@
|
|||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getRequestChannelListenerInjectable } from "../../../main/utils/channel/channel-listeners/listener-tokens";
|
import { getRequestChannelListenerInjectable } from "@k8slens/messaging";
|
||||||
import { casChannel } from "../common/channel";
|
import { casChannel } from "../common/channel";
|
||||||
import { globalAgent } from "https";
|
import { globalAgent } from "https";
|
||||||
import { isString } from "@k8slens/utilities";
|
import { isString } from "@k8slens/utilities";
|
||||||
|
|
||||||
const certificateAuthoritiesChannelListenerInjectable = getRequestChannelListenerInjectable({
|
const certificateAuthoritiesChannelListenerInjectable = getRequestChannelListenerInjectable({
|
||||||
|
id: "certificate-authorities-channel-listener",
|
||||||
channel: casChannel,
|
channel: casChannel,
|
||||||
handler: () => () => {
|
getHandler: () => () => {
|
||||||
if (Array.isArray(globalAgent.options.ca)) {
|
if (Array.isArray(globalAgent.options.ca)) {
|
||||||
return globalAgent.options.ca.filter(isString);
|
return globalAgent.options.ca.filter(isString);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { requestFromChannelInjectionToken } from "../../../common/utils/channel/request-from-channel-injection-token";
|
import { requestFromChannelInjectionToken } from "@k8slens/messaging";
|
||||||
import { casChannel } from "../common/channel";
|
import { casChannel } from "../common/channel";
|
||||||
import { requestSystemCAsInjectionToken } from "../common/request-system-cas-token";
|
import { requestSystemCAsInjectionToken } from "../common/request-system-cas-token";
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import type { ClusterId } from "../../../../common/cluster-types";
|
import type { ClusterId } from "../../../../common/cluster-types";
|
||||||
import { getRequestChannel } from "../../../../common/utils/channel/get-request-channel";
|
import { getRequestChannel } from "@k8slens/messaging";
|
||||||
|
|
||||||
export interface ActivateCluster {
|
export interface ActivateCluster {
|
||||||
clusterId: ClusterId;
|
clusterId: ClusterId;
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { getInjectionToken } from "@ogre-tools/injectable";
|
import { getInjectionToken } from "@ogre-tools/injectable";
|
||||||
import type { ChannelRequester } from "../../../../common/utils/channel/request-from-channel-injection-token";
|
import type { ChannelRequester } from "@k8slens/messaging";
|
||||||
import type { activateClusterChannel, deactivateClusterChannel } from "./channels";
|
import type { activateClusterChannel, deactivateClusterChannel } from "./channels";
|
||||||
|
|
||||||
export type RequestClusterActivation = ChannelRequester<typeof activateClusterChannel>;
|
export type RequestClusterActivation = ChannelRequester<typeof activateClusterChannel>;
|
||||||
|
|||||||
@ -2,13 +2,14 @@
|
|||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getRequestChannelListenerInjectable } from "../../../../main/utils/channel/channel-listeners/listener-tokens";
|
import { getRequestChannelListenerInjectable } from "@k8slens/messaging";
|
||||||
import { activateClusterChannel } from "../common/channels";
|
import { activateClusterChannel } from "../common/channels";
|
||||||
import requestClusterActivationInjectable from "./request-activation.injectable";
|
import requestClusterActivationInjectable from "./request-activation.injectable";
|
||||||
|
|
||||||
const activateClusterRequestChannelListenerInjectable = getRequestChannelListenerInjectable({
|
const activateClusterRequestChannelListenerInjectable = getRequestChannelListenerInjectable({
|
||||||
|
id: "activate-cluster-request-channel-listener",
|
||||||
channel: activateClusterChannel,
|
channel: activateClusterChannel,
|
||||||
handler: (di) => di.inject(requestClusterActivationInjectable),
|
getHandler: (di) => di.inject(requestClusterActivationInjectable),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default activateClusterRequestChannelListenerInjectable;
|
export default activateClusterRequestChannelListenerInjectable;
|
||||||
|
|||||||
@ -2,13 +2,14 @@
|
|||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getRequestChannelListenerInjectable } from "../../../../main/utils/channel/channel-listeners/listener-tokens";
|
import { getRequestChannelListenerInjectable } from "@k8slens/messaging";
|
||||||
import { deactivateClusterChannel } from "../common/channels";
|
import { deactivateClusterChannel } from "../common/channels";
|
||||||
import requestClusterDeactivationInjectable from "./request-deactivation.injectable";
|
import requestClusterDeactivationInjectable from "./request-deactivation.injectable";
|
||||||
|
|
||||||
const clusterDeactivationRequestChannelListenerInjectable = getRequestChannelListenerInjectable({
|
const clusterDeactivationRequestChannelListenerInjectable = getRequestChannelListenerInjectable({
|
||||||
|
id: "cluster-deactivation-request-channel-listener",
|
||||||
channel: deactivateClusterChannel,
|
channel: deactivateClusterChannel,
|
||||||
handler: (di) => di.inject(requestClusterDeactivationInjectable),
|
getHandler: (di) => di.inject(requestClusterDeactivationInjectable),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default clusterDeactivationRequestChannelListenerInjectable;
|
export default clusterDeactivationRequestChannelListenerInjectable;
|
||||||
|
|||||||
@ -3,14 +3,14 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import requestFromChannelInjectable from "../../../../renderer/utils/channel/request-from-channel.injectable";
|
import { requestFromChannelInjectionToken } from "@k8slens/messaging";
|
||||||
import { activateClusterChannel } from "../common/channels";
|
import { activateClusterChannel } from "../common/channels";
|
||||||
import { requestClusterActivationInjectionToken } from "../common/request-token";
|
import { requestClusterActivationInjectionToken } from "../common/request-token";
|
||||||
|
|
||||||
const requestClusterActivationInjectable = getInjectable({
|
const requestClusterActivationInjectable = getInjectable({
|
||||||
id: "request-cluster-activation",
|
id: "request-cluster-activation",
|
||||||
instantiate: (di) => {
|
instantiate: (di) => {
|
||||||
const requestFromChannel = di.inject(requestFromChannelInjectable);
|
const requestFromChannel = di.inject(requestFromChannelInjectionToken);
|
||||||
|
|
||||||
return (req) => requestFromChannel(activateClusterChannel, req);
|
return (req) => requestFromChannel(activateClusterChannel, req);
|
||||||
},
|
},
|
||||||
|
|||||||
@ -3,14 +3,14 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import requestFromChannelInjectable from "../../../../renderer/utils/channel/request-from-channel.injectable";
|
import { requestFromChannelInjectionToken } from "@k8slens/messaging";
|
||||||
import { deactivateClusterChannel } from "../common/channels";
|
import { deactivateClusterChannel } from "../common/channels";
|
||||||
import { requestClusterDeactivationInjectionToken } from "../common/request-token";
|
import { requestClusterDeactivationInjectionToken } from "../common/request-token";
|
||||||
|
|
||||||
const requestClusterDeactivationInjectable = getInjectable({
|
const requestClusterDeactivationInjectable = getInjectable({
|
||||||
id: "request-cluster-deactivation",
|
id: "request-cluster-deactivation",
|
||||||
instantiate: (di) => {
|
instantiate: (di) => {
|
||||||
const requestFromChannel = di.inject(requestFromChannelInjectable);
|
const requestFromChannel = di.inject(requestFromChannelInjectionToken);
|
||||||
|
|
||||||
return (clusterId) => requestFromChannel(deactivateClusterChannel, clusterId);
|
return (clusterId) => requestFromChannel(deactivateClusterChannel, clusterId);
|
||||||
},
|
},
|
||||||
|
|||||||
@ -3,10 +3,8 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import type { ClusterId } from "../../../../common/cluster-types";
|
import type { ClusterId } from "../../../../common/cluster-types";
|
||||||
import type { RequestChannel } from "../../../../common/utils/channel/request-channel-listener-injection-token";
|
import { getRequestChannel } from "@k8slens/messaging";
|
||||||
|
|
||||||
export type ClearClusterAsDeletingChannel = RequestChannel<ClusterId, void>;
|
export const clearClusterAsDeletingChannel = getRequestChannel<ClusterId, void>(
|
||||||
|
"clear-cluster-as-deleting",
|
||||||
export const clearClusterAsDeletingChannel: ClearClusterAsDeletingChannel = {
|
);
|
||||||
id: "clear-cluster-as-deleting",
|
|
||||||
};
|
|
||||||
|
|||||||
@ -3,10 +3,11 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import type { ClusterId } from "../../../../common/cluster-types";
|
import type { ClusterId } from "../../../../common/cluster-types";
|
||||||
import type { RequestChannel } from "../../../../common/utils/channel/request-channel-listener-injection-token";
|
import type { RequestChannel } from "@k8slens/messaging";
|
||||||
|
import { getRequestChannel } from "@k8slens/messaging";
|
||||||
|
|
||||||
export type DeleteClusterChannel = RequestChannel<ClusterId, void>;
|
export type DeleteClusterChannel = RequestChannel<ClusterId, void>;
|
||||||
|
|
||||||
export const deleteClusterChannel: DeleteClusterChannel = {
|
export const deleteClusterChannel = getRequestChannel<ClusterId, void>(
|
||||||
id: "delete-cluster",
|
"delete-cluster",
|
||||||
};
|
);
|
||||||
|
|||||||
@ -3,10 +3,8 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import type { ClusterId } from "../../../../common/cluster-types";
|
import type { ClusterId } from "../../../../common/cluster-types";
|
||||||
import type { RequestChannel } from "../../../../common/utils/channel/request-channel-listener-injection-token";
|
import { getRequestChannel } from "@k8slens/messaging";
|
||||||
|
|
||||||
export type SetClusterAsDeletingChannel = RequestChannel<ClusterId, void>;
|
export const setClusterAsDeletingChannel = getRequestChannel<ClusterId, void>(
|
||||||
|
"set-cluster-as-deleting",
|
||||||
export const setClusterAsDeletingChannel: SetClusterAsDeletingChannel = {
|
);
|
||||||
id: "set-cluster-as-deleting",
|
|
||||||
};
|
|
||||||
|
|||||||
@ -3,12 +3,13 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import clustersThatAreBeingDeletedInjectable from "../../../../main/cluster/are-being-deleted.injectable";
|
import clustersThatAreBeingDeletedInjectable from "../../../../main/cluster/are-being-deleted.injectable";
|
||||||
import { getRequestChannelListenerInjectable } from "../../../../main/utils/channel/channel-listeners/listener-tokens";
|
import { getRequestChannelListenerInjectable } from "@k8slens/messaging";
|
||||||
import { clearClusterAsDeletingChannel } from "../common/clear-as-deleting-channel";
|
import { clearClusterAsDeletingChannel } from "../common/clear-as-deleting-channel";
|
||||||
|
|
||||||
const clearClusterAsDeletingChannelListenerInjectable = getRequestChannelListenerInjectable({
|
const clearClusterAsDeletingChannelListenerInjectable = getRequestChannelListenerInjectable({
|
||||||
|
id: "clear-cluster-as-deleting-channel-listener",
|
||||||
channel: clearClusterAsDeletingChannel,
|
channel: clearClusterAsDeletingChannel,
|
||||||
handler: (di) => {
|
getHandler: (di) => {
|
||||||
const clustersThatAreBeingDeleted = di.inject(clustersThatAreBeingDeletedInjectable);
|
const clustersThatAreBeingDeleted = di.inject(clustersThatAreBeingDeletedInjectable);
|
||||||
|
|
||||||
return (clusterId) => {
|
return (clusterId) => {
|
||||||
|
|||||||
@ -10,12 +10,13 @@ import removePathInjectable from "../../../../common/fs/remove.injectable";
|
|||||||
import joinPathsInjectable from "../../../../common/path/join-paths.injectable";
|
import joinPathsInjectable from "../../../../common/path/join-paths.injectable";
|
||||||
import clusterConnectionInjectable from "../../../../main/cluster/cluster-connection.injectable";
|
import clusterConnectionInjectable from "../../../../main/cluster/cluster-connection.injectable";
|
||||||
import { noop } from "@k8slens/utilities";
|
import { noop } from "@k8slens/utilities";
|
||||||
import { getRequestChannelListenerInjectable } from "../../../../main/utils/channel/channel-listeners/listener-tokens";
|
import { getRequestChannelListenerInjectable } from "@k8slens/messaging";
|
||||||
import { deleteClusterChannel } from "../common/delete-channel";
|
import { deleteClusterChannel } from "../common/delete-channel";
|
||||||
|
|
||||||
const deleteClusterChannelListenerInjectable = getRequestChannelListenerInjectable({
|
const deleteClusterChannelListenerInjectable = getRequestChannelListenerInjectable({
|
||||||
|
id: "delete-cluster-channel-listener",
|
||||||
channel: deleteClusterChannel,
|
channel: deleteClusterChannel,
|
||||||
handler: (di) => {
|
getHandler: (di) => {
|
||||||
const emitAppEvent = di.inject(emitAppEventInjectable);
|
const emitAppEvent = di.inject(emitAppEventInjectable);
|
||||||
const clusterStore = di.inject(clusterStoreInjectable);
|
const clusterStore = di.inject(clusterStoreInjectable);
|
||||||
const clusterFrames = di.inject(clusterFramesInjectable);
|
const clusterFrames = di.inject(clusterFramesInjectable);
|
||||||
|
|||||||
@ -3,12 +3,13 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import clustersThatAreBeingDeletedInjectable from "../../../../main/cluster/are-being-deleted.injectable";
|
import clustersThatAreBeingDeletedInjectable from "../../../../main/cluster/are-being-deleted.injectable";
|
||||||
import { getRequestChannelListenerInjectable } from "../../../../main/utils/channel/channel-listeners/listener-tokens";
|
import { getRequestChannelListenerInjectable } from "@k8slens/messaging";
|
||||||
import { setClusterAsDeletingChannel } from "../common/set-as-deleting-channel";
|
import { setClusterAsDeletingChannel } from "../common/set-as-deleting-channel";
|
||||||
|
|
||||||
const setClusterAsDeletingChannelHandlerInjectable = getRequestChannelListenerInjectable({
|
const setClusterAsDeletingChannelHandlerInjectable = getRequestChannelListenerInjectable({
|
||||||
|
id: "set-cluster-as-deleting-channel-handler",
|
||||||
channel: setClusterAsDeletingChannel,
|
channel: setClusterAsDeletingChannel,
|
||||||
handler: (di) => {
|
getHandler: (di) => {
|
||||||
const clustersThatAreBeingDeleted = di.inject(clustersThatAreBeingDeletedInjectable);
|
const clustersThatAreBeingDeleted = di.inject(clustersThatAreBeingDeletedInjectable);
|
||||||
|
|
||||||
return (clusterId) => {
|
return (clusterId) => {
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import type { ClusterId } from "../../../../common/cluster-types";
|
import type { ClusterId } from "../../../../common/cluster-types";
|
||||||
import requestFromChannelInjectable from "../../../../renderer/utils/channel/request-from-channel.injectable";
|
import { requestFromChannelInjectionToken } from "@k8slens/messaging";
|
||||||
import { clearClusterAsDeletingChannel } from "../common/clear-as-deleting-channel";
|
import { clearClusterAsDeletingChannel } from "../common/clear-as-deleting-channel";
|
||||||
|
|
||||||
export type RequestClearClusterAsDeleting = (clusterId: ClusterId) => Promise<void>;
|
export type RequestClearClusterAsDeleting = (clusterId: ClusterId) => Promise<void>;
|
||||||
@ -12,7 +12,7 @@ export type RequestClearClusterAsDeleting = (clusterId: ClusterId) => Promise<vo
|
|||||||
const requestClearClusterAsDeletingInjectable = getInjectable({
|
const requestClearClusterAsDeletingInjectable = getInjectable({
|
||||||
id: "request-clear-cluster-as-deleting",
|
id: "request-clear-cluster-as-deleting",
|
||||||
instantiate: (di): RequestClearClusterAsDeleting => {
|
instantiate: (di): RequestClearClusterAsDeleting => {
|
||||||
const requestChannel = di.inject(requestFromChannelInjectable);
|
const requestChannel = di.inject(requestFromChannelInjectionToken);
|
||||||
|
|
||||||
return (clusterId) => requestChannel(clearClusterAsDeletingChannel, clusterId);
|
return (clusterId) => requestChannel(clearClusterAsDeletingChannel, clusterId);
|
||||||
},
|
},
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import type { ClusterId } from "../../../../common/cluster-types";
|
import type { ClusterId } from "../../../../common/cluster-types";
|
||||||
import requestFromChannelInjectable from "../../../../renderer/utils/channel/request-from-channel.injectable";
|
import { requestFromChannelInjectionToken } from "@k8slens/messaging";
|
||||||
import { deleteClusterChannel } from "../common/delete-channel";
|
import { deleteClusterChannel } from "../common/delete-channel";
|
||||||
|
|
||||||
export type RequestDeleteCluster = (clusterId: ClusterId) => Promise<void>;
|
export type RequestDeleteCluster = (clusterId: ClusterId) => Promise<void>;
|
||||||
@ -12,7 +12,7 @@ export type RequestDeleteCluster = (clusterId: ClusterId) => Promise<void>;
|
|||||||
const requestDeleteClusterInjectable = getInjectable({
|
const requestDeleteClusterInjectable = getInjectable({
|
||||||
id: "request-delete-cluster",
|
id: "request-delete-cluster",
|
||||||
instantiate: (di): RequestDeleteCluster => {
|
instantiate: (di): RequestDeleteCluster => {
|
||||||
const requestChannel = di.inject(requestFromChannelInjectable);
|
const requestChannel = di.inject(requestFromChannelInjectionToken);
|
||||||
|
|
||||||
return (clusterId) => requestChannel(deleteClusterChannel, clusterId);
|
return (clusterId) => requestChannel(deleteClusterChannel, clusterId);
|
||||||
},
|
},
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import type { ClusterId } from "../../../../common/cluster-types";
|
import type { ClusterId } from "../../../../common/cluster-types";
|
||||||
import requestFromChannelInjectable from "../../../../renderer/utils/channel/request-from-channel.injectable";
|
import { requestFromChannelInjectionToken } from "@k8slens/messaging";
|
||||||
import { setClusterAsDeletingChannel } from "../common/set-as-deleting-channel";
|
import { setClusterAsDeletingChannel } from "../common/set-as-deleting-channel";
|
||||||
|
|
||||||
export type RequestSetClusterAsDeleting = (clusterId: ClusterId) => Promise<void>;
|
export type RequestSetClusterAsDeleting = (clusterId: ClusterId) => Promise<void>;
|
||||||
@ -12,7 +12,7 @@ export type RequestSetClusterAsDeleting = (clusterId: ClusterId) => Promise<void
|
|||||||
const requestSetClusterAsDeletingInjectable = getInjectable({
|
const requestSetClusterAsDeletingInjectable = getInjectable({
|
||||||
id: "request-set-cluster-as-deleting",
|
id: "request-set-cluster-as-deleting",
|
||||||
instantiate: (di): RequestSetClusterAsDeleting => {
|
instantiate: (di): RequestSetClusterAsDeleting => {
|
||||||
const requestChannel = di.inject(requestFromChannelInjectable);
|
const requestChannel = di.inject(requestFromChannelInjectionToken);
|
||||||
|
|
||||||
return (clusterId) => requestChannel(setClusterAsDeletingChannel, clusterId);
|
return (clusterId) => requestChannel(setClusterAsDeletingChannel, clusterId);
|
||||||
},
|
},
|
||||||
|
|||||||
@ -0,0 +1,637 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { AsyncFnMock } from "@async-fn/jest";
|
||||||
|
import asyncFn from "@async-fn/jest";
|
||||||
|
import type { AuthorizationV1Api, CoreV1Api, V1APIGroupList, V1APIVersions, V1NamespaceList, V1SelfSubjectAccessReview, V1SelfSubjectRulesReview } from "@kubernetes/client-node";
|
||||||
|
import clusterStoreInjectable from "../../common/cluster-store/cluster-store.injectable";
|
||||||
|
import type { Cluster } from "../../common/cluster/cluster";
|
||||||
|
import createAuthorizationApiInjectable from "../../common/cluster/create-authorization-api.injectable";
|
||||||
|
import writeJsonFileInjectable from "../../common/fs/write-json-file.injectable";
|
||||||
|
import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
|
||||||
|
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
|
||||||
|
import broadcastMessageInjectable from "../../common/ipc/broadcast-message.injectable";
|
||||||
|
import type { PartialDeep } from "type-fest";
|
||||||
|
import { anyObject } from "jest-mock-extended";
|
||||||
|
import createCoreApiInjectable from "../../common/cluster/create-core-api.injectable";
|
||||||
|
import type { K8sRequest } from "../../main/k8s-request.injectable";
|
||||||
|
import k8sRequestInjectable from "../../main/k8s-request.injectable";
|
||||||
|
import type { DetectClusterMetadata } from "../../main/cluster-detectors/detect-cluster-metadata.injectable";
|
||||||
|
import detectClusterMetadataInjectable from "../../main/cluster-detectors/detect-cluster-metadata.injectable";
|
||||||
|
import type { ClusterConnection } from "../../main/cluster/cluster-connection.injectable";
|
||||||
|
import clusterConnectionInjectable from "../../main/cluster/cluster-connection.injectable";
|
||||||
|
import type { KubeAuthProxy } from "../../main/kube-auth-proxy/create-kube-auth-proxy.injectable";
|
||||||
|
import createKubeAuthProxyInjectable from "../../main/kube-auth-proxy/create-kube-auth-proxy.injectable";
|
||||||
|
import type { Mocked } from "../../test-utils/mock-interface";
|
||||||
|
import { flushPromises } from "@k8slens/test-utils";
|
||||||
|
|
||||||
|
describe("Refresh Cluster Accessibility Technical Tests", () => {
|
||||||
|
let builder: ApplicationBuilder;
|
||||||
|
let createSelfSubjectRulesReviewMock: AsyncFnMock<AuthorizationV1Api["createSelfSubjectRulesReview"]>;
|
||||||
|
let createSelfSubjectAccessReviewMock: AsyncFnMock<AuthorizationV1Api["createSelfSubjectAccessReview"]>;
|
||||||
|
let listNamespaceMock: AsyncFnMock<CoreV1Api["listNamespace"]>;
|
||||||
|
let k8sRequestMock: AsyncFnMock<K8sRequest>;
|
||||||
|
let detectClusterMetadataMock: AsyncFnMock<DetectClusterMetadata>;
|
||||||
|
let kubeAuthProxyMock: Mocked<KubeAuthProxy>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
builder = getApplicationBuilder();
|
||||||
|
|
||||||
|
const mainDi = builder.mainDi;
|
||||||
|
|
||||||
|
mainDi.override(broadcastMessageInjectable, () => async () => {});
|
||||||
|
|
||||||
|
kubeAuthProxyMock = {
|
||||||
|
apiPrefix: "/some-api-prefix",
|
||||||
|
port: 0,
|
||||||
|
exit: jest.fn(),
|
||||||
|
run: asyncFn(),
|
||||||
|
};
|
||||||
|
mainDi.override(createKubeAuthProxyInjectable, () => () => kubeAuthProxyMock);
|
||||||
|
|
||||||
|
detectClusterMetadataMock = asyncFn();
|
||||||
|
mainDi.override(detectClusterMetadataInjectable, () => detectClusterMetadataMock);
|
||||||
|
|
||||||
|
k8sRequestMock = asyncFn();
|
||||||
|
mainDi.override(k8sRequestInjectable, () => k8sRequestMock);
|
||||||
|
|
||||||
|
createSelfSubjectRulesReviewMock = asyncFn();
|
||||||
|
createSelfSubjectAccessReviewMock = asyncFn();
|
||||||
|
mainDi.override(createAuthorizationApiInjectable, () => () => ({
|
||||||
|
createSelfSubjectRulesReview: createSelfSubjectRulesReviewMock,
|
||||||
|
createSelfSubjectAccessReview: createSelfSubjectAccessReviewMock,
|
||||||
|
} as any));
|
||||||
|
|
||||||
|
listNamespaceMock = asyncFn();
|
||||||
|
mainDi.override(createCoreApiInjectable, () => () => ({
|
||||||
|
listNamespace: listNamespaceMock,
|
||||||
|
} as any));
|
||||||
|
|
||||||
|
await builder.render();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("given a cluster with no configured preferences", () => {
|
||||||
|
let cluster: Cluster;
|
||||||
|
let clusterConnection: ClusterConnection;
|
||||||
|
let refreshPromise: Promise<void>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const mainDi = builder.mainDi;
|
||||||
|
const clusterStore = mainDi.inject(clusterStoreInjectable);
|
||||||
|
const writeJsonFile = mainDi.inject(writeJsonFileInjectable);
|
||||||
|
|
||||||
|
await writeJsonFile("/some-kube-config-path", {
|
||||||
|
apiVersion: "v1",
|
||||||
|
kind: "Config",
|
||||||
|
clusters: [{
|
||||||
|
name: "some-cluster-name",
|
||||||
|
cluster: {
|
||||||
|
server: "https://localhost:8989",
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
users: [{
|
||||||
|
name: "some-user-name",
|
||||||
|
}],
|
||||||
|
contexts: [{
|
||||||
|
name: "some-cluster-context",
|
||||||
|
context: {
|
||||||
|
user: "some-user-name",
|
||||||
|
cluster: "some-cluster-name",
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
|
||||||
|
clusterStore.addCluster({
|
||||||
|
contextName: "some-cluster-context",
|
||||||
|
id: "some-cluster-id",
|
||||||
|
kubeConfigPath: "/some-kube-config-path",
|
||||||
|
});
|
||||||
|
|
||||||
|
cluster = clusterStore.getById("some-cluster-id") ?? (() => { throw new Error("missing cluster"); })();
|
||||||
|
clusterConnection = mainDi.inject(clusterConnectionInjectable, cluster);
|
||||||
|
refreshPromise = clusterConnection.refreshAccessibilityAndMetadata();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("starts kubeAuthProxy", () => {
|
||||||
|
expect(kubeAuthProxyMock.run).toBeCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when kubeAuthProxy has started running and its port is found", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
kubeAuthProxyMock.port = 1235;
|
||||||
|
await kubeAuthProxyMock.run.resolve();
|
||||||
|
await flushPromises();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("requests if cluster has admin permissions", async () => {
|
||||||
|
expect(createSelfSubjectAccessReviewMock).toBeCalledWith(anyObject({
|
||||||
|
spec: {
|
||||||
|
namespace: "kube-system",
|
||||||
|
resource: "*",
|
||||||
|
verb: "create",
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe.each([ true, false ])("when cluster admin request resolves to %p", (isAdmin) => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await createSelfSubjectAccessReviewMock.resolve({
|
||||||
|
body: {
|
||||||
|
status: {
|
||||||
|
allowed: isAdmin,
|
||||||
|
},
|
||||||
|
} as PartialDeep<V1SelfSubjectAccessReview>,
|
||||||
|
} as any);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("requests if cluster has global watch permissions", () => {
|
||||||
|
expect(createSelfSubjectAccessReviewMock).toBeCalledWith(anyObject({
|
||||||
|
spec: {
|
||||||
|
verb: "watch",
|
||||||
|
resource: "*",
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe.each([ true, false ])("when cluster global watch request resolves with %p", (globalWatch) => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await createSelfSubjectAccessReviewMock.resolve({
|
||||||
|
body: {
|
||||||
|
status: {
|
||||||
|
allowed: globalWatch,
|
||||||
|
},
|
||||||
|
} as PartialDeep<V1SelfSubjectAccessReview>,
|
||||||
|
} as any);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("requests namespaces", () => {
|
||||||
|
expect(listNamespaceMock).toBeCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when list namespaces resolves", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await listNamespaceMock.resolve(listNamespaceResponse);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("requests core api versions", () => {
|
||||||
|
expect(k8sRequestMock).toBeCalledWith(
|
||||||
|
anyObject({ id: "some-cluster-id" }),
|
||||||
|
"/api",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when core api versions request resolves", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await k8sRequestMock.resolve({
|
||||||
|
serverAddressByClientCIDRs: [],
|
||||||
|
versions: [
|
||||||
|
"v1",
|
||||||
|
],
|
||||||
|
} as V1APIVersions);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("requests non-core api resource kinds", () => {
|
||||||
|
expect(k8sRequestMock).toBeCalledWith(
|
||||||
|
anyObject({ id: "some-cluster-id" }),
|
||||||
|
"/apis",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when non-core api resource kinds request resolves", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await k8sRequestMock.resolve(nonCoreApiResponse);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("requests specific resource kinds in core", () => {
|
||||||
|
expect(k8sRequestMock).toBeCalledWith(
|
||||||
|
anyObject({ id: "some-cluster-id" }),
|
||||||
|
"/api/v1",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when core specific resource kinds request resolves", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await k8sRequestMock.resolve(coreApiKindsResponse);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("requests specific resources kinds from the first non-core response", () => {
|
||||||
|
expect(k8sRequestMock).toBeCalledWith(
|
||||||
|
anyObject({ id: "some-cluster-id" }),
|
||||||
|
"/apis/node.k8s.io/v1",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when first specific resource kinds request resolves", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await k8sRequestMock.resolve(nodeK8sIoKindsResponse);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("requests specific resources kinds from the second non-core response", () => {
|
||||||
|
expect(k8sRequestMock).toBeCalledWith(
|
||||||
|
anyObject({ id: "some-cluster-id" }),
|
||||||
|
"/apis/discovery.k8s.io/v1",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when second specific resource kinds request resolves", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await k8sRequestMock.resolve(discoveryK8sIoKindsResponse);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("requests namespace list permissions for 'default' namespace", () => {
|
||||||
|
expect(createSelfSubjectRulesReviewMock).toBeCalledWith(anyObject({
|
||||||
|
spec: {
|
||||||
|
namespace: "default",
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when the permissions are incomplete", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await createSelfSubjectRulesReviewMock.resolve(defaultIncompletePermissions);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("requests namespace list permissions for 'my-namespace' namespace", () => {
|
||||||
|
expect(createSelfSubjectRulesReviewMock).toBeCalledWith(anyObject({
|
||||||
|
spec: {
|
||||||
|
namespace: "my-namespace",
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when the permissions request for 'my-namespace' resolves as empty", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await createSelfSubjectRulesReviewMock.resolve(emptyPermissions);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("requests cluster metadata", () => {
|
||||||
|
expect(detectClusterMetadataMock).toBeCalledWith(anyObject({ id: "some-cluster-id" }));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when cluster metadata request resolves", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await detectClusterMetadataMock.resolve({});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows the call to refreshAccessibilityAndMetadata to resolve", async () => {
|
||||||
|
await refreshPromise;
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should have the cluster displaying 'pods'", () => {
|
||||||
|
expect(cluster.resourcesToShow.has("pods")).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should have the cluster displaying 'namespaces'", () => {
|
||||||
|
expect(cluster.resourcesToShow.has("namespaces")).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe.skip("when the permissions are incomplete", () => {});
|
||||||
|
describe.skip("when the permissions resolve to a single entry with 'list' verb", () => {});
|
||||||
|
describe.skip("when the permissions resolve to multiple entries with the 'list' verb not on the first entry", () => {});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when the permissions resolve to an empty list", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await createSelfSubjectRulesReviewMock.resolve(emptyPermissions);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("requests namespace list permissions for 'my-namespace' namespace", () => {
|
||||||
|
expect(createSelfSubjectRulesReviewMock).toBeCalledWith(anyObject({
|
||||||
|
spec: {
|
||||||
|
namespace: "my-namespace",
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when the permissions request for 'my-namespace' resolves as empty", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await createSelfSubjectRulesReviewMock.resolve(emptyPermissions);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("requests cluster metadata", () => {
|
||||||
|
expect(detectClusterMetadataMock).toBeCalledWith(anyObject({ id: "some-cluster-id" }));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when cluster metadata request resolves", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await detectClusterMetadataMock.resolve({});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows the call to refreshAccessibilityAndMetadata to resolve", async () => {
|
||||||
|
await refreshPromise;
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should have the cluster displaying 'pods'", () => {
|
||||||
|
expect(cluster.resourcesToShow.has("pods")).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should have the cluster not displaying 'namespaces'", () => {
|
||||||
|
expect(cluster.resourcesToShow.has("namespaces")).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe.skip("when the permissions are incomplete", () => {});
|
||||||
|
describe.skip("when the permissions resolve to a single entry with 'list' verb", () => {});
|
||||||
|
describe.skip("when the permissions resolve to multiple entries with the 'list' verb not on the first entry", () => {});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when the permissions resolve to a single entry with 'list' verb", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await createSelfSubjectRulesReviewMock.resolve(defaultSingleListPermissions);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("requests namespace list permissions for 'my-namespace' namespace", () => {
|
||||||
|
expect(createSelfSubjectRulesReviewMock).toBeCalledWith(anyObject({
|
||||||
|
spec: {
|
||||||
|
namespace: "my-namespace",
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when the permissions request for 'my-namespace' resolves as empty", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await createSelfSubjectRulesReviewMock.resolve(emptyPermissions);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("requests cluster metadata", () => {
|
||||||
|
expect(detectClusterMetadataMock).toBeCalledWith(anyObject({ id: "some-cluster-id" }));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when cluster metadata request resolves", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await detectClusterMetadataMock.resolve({});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows the call to refreshAccessibilityAndMetadata to resolve", async () => {
|
||||||
|
await refreshPromise;
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should have the cluster displaying 'pods'", () => {
|
||||||
|
expect(cluster.resourcesToShow.has("pods")).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should have the cluster not displaying 'namespaces'", () => {
|
||||||
|
expect(cluster.resourcesToShow.has("namespaces")).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe.skip("when the permissions are incomplete", () => {});
|
||||||
|
describe.skip("when the permissions resolve to a single entry with 'list' verb", () => {});
|
||||||
|
describe.skip("when the permissions resolve to multiple entries with the 'list' verb not on the first entry", () => {});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when the permissions resolve to multiple entries with the 'list' verb not on the first entry", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await createSelfSubjectRulesReviewMock.resolve(defaultMultipleListPermissions);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("requests namespace list permissions for 'my-namespace' namespace", () => {
|
||||||
|
expect(createSelfSubjectRulesReviewMock).toBeCalledWith(anyObject({
|
||||||
|
spec: {
|
||||||
|
namespace: "my-namespace",
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when the permissions request for 'my-namespace' resolves as empty", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await createSelfSubjectRulesReviewMock.resolve(emptyPermissions);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("requests cluster metadata", () => {
|
||||||
|
expect(detectClusterMetadataMock).toBeCalledWith(anyObject({ id: "some-cluster-id" }));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when cluster metadata request resolves", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await detectClusterMetadataMock.resolve({});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows the call to refreshAccessibilityAndMetadata to resolve", async () => {
|
||||||
|
await refreshPromise;
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should have the cluster displaying 'pods'", () => {
|
||||||
|
expect(cluster.resourcesToShow.has("pods")).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should have the cluster not displaying 'namespaces'", () => {
|
||||||
|
expect(cluster.resourcesToShow.has("namespaces")).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe.skip("when the permissions are incomplete", () => {});
|
||||||
|
describe.skip("when the permissions resolve to a single entry with 'list' verb", () => {});
|
||||||
|
describe.skip("when the permissions resolve to multiple entries with the 'list' verb not on the first entry", () => {});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe.skip("when second specific resource kinds rejects", () => {});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe.skip("when first specific resource kinds rejects", () => {});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const nonCoreApiResponse = {
|
||||||
|
groups: [
|
||||||
|
{
|
||||||
|
name: "node.k8s.io",
|
||||||
|
versions: [
|
||||||
|
{
|
||||||
|
groupVersion: "node.k8s.io/v1",
|
||||||
|
version: "v1",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
preferredVersion: {
|
||||||
|
groupVersion: "node.k8s.io/v1",
|
||||||
|
version: "v1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "discovery.k8s.io",
|
||||||
|
versions: [
|
||||||
|
{
|
||||||
|
groupVersion: "discovery.k8s.io/v1",
|
||||||
|
version: "v1",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
preferredVersion: {
|
||||||
|
groupVersion: "discovery.k8s.io/v1",
|
||||||
|
version: "v1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as V1APIGroupList;
|
||||||
|
|
||||||
|
const listNamespaceResponse = {
|
||||||
|
body: {
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
metadata: {
|
||||||
|
name: "default",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
metadata: {
|
||||||
|
name: "my-namespace",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as PartialDeep<V1NamespaceList>,
|
||||||
|
} as Awaited<ReturnType<CoreV1Api["listNamespace"]>>;
|
||||||
|
|
||||||
|
const coreApiKindsResponse = {
|
||||||
|
kind: "APIResourceList",
|
||||||
|
groupVersion: "v1",
|
||||||
|
resources: [
|
||||||
|
{
|
||||||
|
name: "namespaces",
|
||||||
|
singularName: "",
|
||||||
|
namespaced: false,
|
||||||
|
kind: "Namespace",
|
||||||
|
verbs: ["create", "delete", "get", "list", "patch", "update", "watch"],
|
||||||
|
shortNames: ["ns"],
|
||||||
|
storageVersionHash: "Q3oi5N2YM8M=",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "pods",
|
||||||
|
singularName: "",
|
||||||
|
namespaced: true,
|
||||||
|
kind: "Pod",
|
||||||
|
verbs: [
|
||||||
|
"create",
|
||||||
|
"delete",
|
||||||
|
"deletecollection",
|
||||||
|
"get",
|
||||||
|
"list",
|
||||||
|
"patch",
|
||||||
|
"update",
|
||||||
|
"watch",
|
||||||
|
],
|
||||||
|
shortNames: ["po"],
|
||||||
|
categories: ["all"],
|
||||||
|
storageVersionHash: "xPOwRZ+Yhw8=",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "pods/attach",
|
||||||
|
singularName: "",
|
||||||
|
namespaced: true,
|
||||||
|
kind: "PodAttachOptions",
|
||||||
|
verbs: ["create", "get"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const nodeK8sIoKindsResponse = {
|
||||||
|
kind: "APIResourceList",
|
||||||
|
apiVersion: "v1",
|
||||||
|
groupVersion: "node.k8s.io/v1",
|
||||||
|
resources: [
|
||||||
|
{
|
||||||
|
name: "runtimeclasses",
|
||||||
|
singularName: "",
|
||||||
|
namespaced: false,
|
||||||
|
kind: "RuntimeClass",
|
||||||
|
verbs: [
|
||||||
|
"create",
|
||||||
|
"delete",
|
||||||
|
"deletecollection",
|
||||||
|
"get",
|
||||||
|
"list",
|
||||||
|
"patch",
|
||||||
|
"update",
|
||||||
|
"watch",
|
||||||
|
],
|
||||||
|
storageVersionHash: "WQTu1GL3T2Q=",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const discoveryK8sIoKindsResponse = {
|
||||||
|
kind: "APIResourceList",
|
||||||
|
apiVersion: "v1",
|
||||||
|
groupVersion: "discovery.k8s.io/v1",
|
||||||
|
resources: [
|
||||||
|
{
|
||||||
|
name: "endpointslices",
|
||||||
|
singularName: "",
|
||||||
|
namespaced: true,
|
||||||
|
kind: "EndpointSlice",
|
||||||
|
verbs: [
|
||||||
|
"create",
|
||||||
|
"delete",
|
||||||
|
"deletecollection",
|
||||||
|
"get",
|
||||||
|
"list",
|
||||||
|
"patch",
|
||||||
|
"update",
|
||||||
|
"watch",
|
||||||
|
],
|
||||||
|
storageVersionHash: "Nx3SIv6I0mE=",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
type CreateSelfSubjectRulesReviewRes = Awaited<ReturnType<AuthorizationV1Api["createSelfSubjectRulesReview"]>>;
|
||||||
|
|
||||||
|
const defaultIncompletePermissions = {
|
||||||
|
body: {
|
||||||
|
status: {
|
||||||
|
incomplete: true,
|
||||||
|
},
|
||||||
|
} as PartialDeep<V1SelfSubjectRulesReview>,
|
||||||
|
} as CreateSelfSubjectRulesReviewRes;
|
||||||
|
|
||||||
|
const emptyPermissions = {
|
||||||
|
body: {
|
||||||
|
status: {
|
||||||
|
resourceRules: [],
|
||||||
|
},
|
||||||
|
} as PartialDeep<V1SelfSubjectRulesReview>,
|
||||||
|
} as CreateSelfSubjectRulesReviewRes;
|
||||||
|
|
||||||
|
const defaultSingleListPermissions = {
|
||||||
|
body: {
|
||||||
|
status: {
|
||||||
|
resourceRules: [{
|
||||||
|
apiGroups: [""],
|
||||||
|
resources: ["pods"],
|
||||||
|
verbs: ["list"],
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
} as PartialDeep<V1SelfSubjectRulesReview>,
|
||||||
|
} as CreateSelfSubjectRulesReviewRes;
|
||||||
|
|
||||||
|
const defaultMultipleListPermissions = {
|
||||||
|
body: {
|
||||||
|
status: {
|
||||||
|
resourceRules: [
|
||||||
|
{
|
||||||
|
apiGroups: [""],
|
||||||
|
resources: ["pods"],
|
||||||
|
verbs: ["get"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
apiGroups: [""],
|
||||||
|
resources: ["pods"],
|
||||||
|
verbs: ["list"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
} as PartialDeep<V1SelfSubjectRulesReview>,
|
||||||
|
} as CreateSelfSubjectRulesReviewRes;
|
||||||
@ -4,8 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import type { ClusterId, ClusterState } from "../../../../common/cluster-types";
|
import type { ClusterId, ClusterState } from "../../../../common/cluster-types";
|
||||||
import type { MessageChannel } from "../../../../common/utils/channel/message-channel-listener-injection-token";
|
import type { MessageChannel, RequestChannel } from "@k8slens/messaging";
|
||||||
import type { RequestChannel } from "../../../../common/utils/channel/request-channel-listener-injection-token";
|
|
||||||
|
|
||||||
export interface ClusterStateSync {
|
export interface ClusterStateSync {
|
||||||
clusterId: ClusterId;
|
clusterId: ClusterId;
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user