mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Release 6.1.13 (#6463)
* Release 6.1.13 Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix kubeconfig-sync sometimes producing multiple identical entities (#5855) Signed-off-by: Sebastian Malton <sebastian@malton.name> * Require milestones on PRs before merging (#6431) * Require milestones on PRs before merging Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix AppImage windows not showing the application icon (#6444) Signed-off-by: Damien Degois <damien@degois.info> Signed-off-by: Damien Degois <damien@degois.info> * Fix links in Readme (#6441) Signed-off-by: Sebastian Malton <sebastian@malton.name> Signed-off-by: Sebastian Malton <sebastian@malton.name> * Update all links within application (#6442) - URLs removed the /latest/ and /main/ pathname prefixes Signed-off-by: Sebastian Malton <sebastian@malton.name> Signed-off-by: Sebastian Malton <sebastian@malton.name> * Bump lens-k8s-proxy to v0.3.0 (#6461) Signed-off-by: Sebastian Malton <sebastian@malton.name> Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix syncing shell env on TCSH and CSH (#6453) * Fix syncing shell env on TCSH and CSH Signed-off-by: Sebastian Malton <sebastian@malton.name> * Refactor computeUnixShellEnvironment to be clearer Signed-off-by: Sebastian Malton <sebastian@malton.name> Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix manually set prometheus service address to work after re-connect (#6435) * Fix manually set prometheus service address to work after re-connect Signed-off-by: Lauri Nevala <lauri.nevala@gmail.com> * Fix manually set prometheus service address to work after re-connect Signed-off-by: Lauri Nevala <lauri.nevala@gmail.com> * Setup prometheus also on contenxt handler constructor Signed-off-by: Lauri Nevala <lauri.nevala@gmail.com> Signed-off-by: Lauri Nevala <lauri.nevala@gmail.com> * Temp fix Signed-off-by: Sebastian Malton <sebastian@malton.name> Signed-off-by: Sebastian Malton <sebastian@malton.name> Signed-off-by: Damien Degois <damien@degois.info> Signed-off-by: Lauri Nevala <lauri.nevala@gmail.com> Co-authored-by: Damien Degois <damien@degois.info> Co-authored-by: Lauri Nevala <lauri.nevala@gmail.com>
This commit is contained in:
parent
40c81d74f3
commit
ab3e24fc54
6
.github/workflows/mkdocs-manual.yml
vendored
6
.github/workflows/mkdocs-manual.yml
vendored
@ -53,9 +53,9 @@ jobs:
|
|||||||
rm -fr ./docs/clusters ./docs/contributing ./docs/faq ./docs/getting-started ./docs/helm ./docs/support ./docs/supporting
|
rm -fr ./docs/clusters ./docs/contributing ./docs/faq ./docs/getting-started ./docs/helm ./docs/support ./docs/supporting
|
||||||
sed -i '/Protocol Handlers/d' ./mkdocs.yml
|
sed -i '/Protocol Handlers/d' ./mkdocs.yml
|
||||||
sed -i '/IPC/d' ./mkdocs.yml
|
sed -i '/IPC/d' ./mkdocs.yml
|
||||||
sed -i 's#../../clusters/adding-clusters.md#https://docs.k8slens.dev/latest/clusters/adding-clusters/#g' ./docs/extensions/get-started/your-first-extension.md
|
sed -i 's#../../clusters/adding-clusters.md#https://docs.k8slens.dev/getting-started/add-cluster/#g' ./docs/extensions/get-started/your-first-extension.md
|
||||||
sed -i 's#clusters/adding-clusters.md#https://docs.k8slens.dev/latest/clusters/adding-clusters/#g' ./docs/README.md
|
sed -i 's#clusters/adding-clusters.md#https://docs.k8slens.dev//getting-started/adding-clusters/#g' ./docs/README.md
|
||||||
sed -i 's#../../contributing/README.md#https://docs.k8slens.dev/latest/contributing/#g' ./docs/extensions/guides/generator.md
|
sed -i 's#../../contributing/README.md#https://docs.k8slens.dev/contributing/#g' ./docs/extensions/guides/generator.md
|
||||||
|
|
||||||
- name: git config
|
- name: git config
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
14
.github/workflows/require-milestone.yml
vendored
Normal file
14
.github/workflows/require-milestone.yml
vendored
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
name: Require Milestone
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types: [opened, edited, synchronize]
|
||||||
|
jobs:
|
||||||
|
milestone:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Require Milestone
|
||||||
|
run: |
|
||||||
|
exit $(gh pr view ${{ github.event.pull_request.number }} --json milestone | jq 'if .milestone == null then 1 else 0 end')
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||||
@ -1,3 +1,3 @@
|
|||||||
# Contributing to Lens
|
# Contributing to Lens
|
||||||
|
|
||||||
See [Contributing to Lens](https://docs.k8slens.dev/latest/contributing/) documentation.
|
See [Contributing to Lens](https://docs.k8slens.dev/contributing/) documentation.
|
||||||
|
|||||||
@ -19,15 +19,15 @@ Lens IDE a standalone application for MacOS, Windows and Linux operating systems
|
|||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
See [Getting Started](https://docs.k8slens.dev/main/getting-started/install-lens/) page.
|
See [Getting Started](https://docs.k8slens.dev/getting-started/install-lens/) page.
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
See [Development](https://docs.k8slens.dev/latest/contributing/development/) page.
|
See [Development](https://docs.k8slens.dev/contributing/development/) page.
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
See [Contributing](https://docs.k8slens.dev/latest/contributing/) page.
|
See [Contributing](https://docs.k8slens.dev/contributing/) page.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|||||||
@ -78,7 +78,7 @@ npm run dev
|
|||||||
You must restart Lens for the extension to load.
|
You must restart Lens for the extension to load.
|
||||||
After this initial restart, reload Lens and it will automatically pick up changes any time the extension rebuilds.
|
After this initial restart, reload Lens and it will automatically pick up changes any time the extension rebuilds.
|
||||||
|
|
||||||
With Lens running, either connect to an existing cluster or create a new one - refer to the latest [Lens Documentation](https://docs.k8slens.dev/main/catalog/) for details on how to add a cluster in Lens IDE.
|
With Lens running, either connect to an existing cluster or create a new one - refer to the latest [Lens Documentation](https://docs.k8slens.dev/getting-started/add-cluster/) for details on how to add a cluster in Lens IDE.
|
||||||
You will see the "Hello World" page in the left-side cluster menu.
|
You will see the "Hello World" page in the left-side cluster menu.
|
||||||
|
|
||||||
## Develop the Extension
|
## Develop the Extension
|
||||||
|
|||||||
@ -46,14 +46,14 @@ Open `my-first-lens-ext/renderer.tsx` and change the value of `title` from `"Hel
|
|||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
clusterPageMenus = [
|
clusterPageMenus = [
|
||||||
{
|
{
|
||||||
target: { pageId: "hello" },
|
target: { pageId: "hello" },
|
||||||
title: "Hello Lens",
|
title: "Hello Lens",
|
||||||
components: {
|
components: {
|
||||||
Icon: ExampleIcon,
|
Icon: ExampleIcon,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
]
|
];
|
||||||
```
|
```
|
||||||
|
|
||||||
Reload Lens and you will see that the menu item text has changed to "Hello Lens".
|
Reload Lens and you will see that the menu item text has changed to "Hello Lens".
|
||||||
@ -70,6 +70,6 @@ To debug your extension, please see our instructions on [Testing Extensions](../
|
|||||||
To dive deeper, consider looking at [Common Capabilities](../capabilities/common-capabilities.md), [Styling](../capabilities/styling.md), or [Extension Anatomy](anatomy.md).
|
To dive deeper, consider looking at [Common Capabilities](../capabilities/common-capabilities.md), [Styling](../capabilities/styling.md), or [Extension Anatomy](anatomy.md).
|
||||||
|
|
||||||
If you find problems with the Lens Extension Generator, or have feature requests, you are welcome to raise an [issue](https://github.com/lensapp/generator-lens-ext/issues).
|
If you find problems with the Lens Extension Generator, or have feature requests, you are welcome to raise an [issue](https://github.com/lensapp/generator-lens-ext/issues).
|
||||||
You can find the latest Lens contribution guidelines [here](https://docs.k8slens.dev/latest/contributing).
|
You can find the latest Lens contribution guidelines [here](https://docs.k8slens.dev/contributing).
|
||||||
|
|
||||||
The Generator source code is hosted at [GitHub](https://github.com/lensapp/generator-lens-ext).
|
The Generator source code is hosted at [GitHub](https://github.com/lensapp/generator-lens-ext).
|
||||||
|
|||||||
@ -771,7 +771,7 @@ Construct the table using the `Renderer.Component.Table` and related elements.
|
|||||||
|
|
||||||
For each pod the name, age, and status are obtained using the `Renderer.K8sApi.Pod` methods.
|
For each pod the name, age, and status are obtained using the `Renderer.K8sApi.Pod` methods.
|
||||||
The table is constructed using the `Renderer.Component.Table` and related elements.
|
The table is constructed using the `Renderer.Component.Table` and related elements.
|
||||||
See [Component documentation](https://docs.k8slens.dev/latest/extensions/api/modules/_renderer_api_components_/) for further details.
|
See [Component documentation](https://api-docs.k8slens.dev/latest/extensions/api/modules/Renderer.Component/) for further details.
|
||||||
|
|
||||||
### `kubeObjectStatusTexts`
|
### `kubeObjectStatusTexts`
|
||||||
|
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
"productName": "OpenLens",
|
"productName": "OpenLens",
|
||||||
"description": "OpenLens - Open Source IDE for Kubernetes",
|
"description": "OpenLens - Open Source IDE for Kubernetes",
|
||||||
"homepage": "https://github.com/lensapp/lens",
|
"homepage": "https://github.com/lensapp/lens",
|
||||||
"version": "6.1.12",
|
"version": "6.1.13",
|
||||||
"main": "static/build/main.js",
|
"main": "static/build/main.js",
|
||||||
"copyright": "© 2022 OpenLens Authors",
|
"copyright": "© 2022 OpenLens Authors",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@ -52,7 +52,7 @@
|
|||||||
"create-release-pr": "node ./scripts/create-release-pr.mjs"
|
"create-release-pr": "node ./scripts/create-release-pr.mjs"
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"k8sProxyVersion": "0.2.1",
|
"k8sProxyVersion": "0.3.0",
|
||||||
"bundledKubectlVersion": "1.23.3",
|
"bundledKubectlVersion": "1.23.3",
|
||||||
"bundledHelmVersion": "3.7.2",
|
"bundledHelmVersion": "3.7.2",
|
||||||
"sentryDsn": "",
|
"sentryDsn": "",
|
||||||
|
|||||||
21
src/common/cluster-store/get-by-id.injectable.ts
Normal file
21
src/common/cluster-store/get-by-id.injectable.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
/**
|
||||||
|
* 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 { ClusterId } from "../cluster-types";
|
||||||
|
import type { Cluster } from "../cluster/cluster";
|
||||||
|
import clusterStoreInjectable from "./cluster-store.injectable";
|
||||||
|
|
||||||
|
export type GetClusterById = (id: ClusterId) => Cluster | undefined;
|
||||||
|
|
||||||
|
const getClusterByIdInjectable = getInjectable({
|
||||||
|
id: "get-cluster-by-id",
|
||||||
|
instantiate: (di): GetClusterById => {
|
||||||
|
const store = di.inject(clusterStoreInjectable);
|
||||||
|
|
||||||
|
return (id) => store.getById(id);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default getClusterByIdInjectable;
|
||||||
30
src/common/fs/create-read-file-stream.injectable.ts
Normal file
30
src/common/fs/create-read-file-stream.injectable.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
/**
|
||||||
|
* 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 { ReadStream } from "fs";
|
||||||
|
import fsInjectable from "./fs.injectable";
|
||||||
|
|
||||||
|
export interface CreateReadStreamOptions {
|
||||||
|
mode?: number;
|
||||||
|
end?: number | undefined;
|
||||||
|
flags?: string | undefined;
|
||||||
|
encoding?: BufferEncoding | undefined;
|
||||||
|
autoClose?: boolean | undefined;
|
||||||
|
/**
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
emitClose?: boolean | undefined;
|
||||||
|
start?: number | undefined;
|
||||||
|
highWaterMark?: number | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CreateReadFileStream = (filePath: string, options?: CreateReadStreamOptions) => ReadStream;
|
||||||
|
|
||||||
|
const createReadFileStreamInjectable = getInjectable({
|
||||||
|
id: "create-read-file-stream",
|
||||||
|
instantiate: (di): CreateReadFileStream => di.inject(fsInjectable).createReadStream,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default createReadFileStreamInjectable;
|
||||||
@ -3,12 +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 type { Stats } from "fs";
|
||||||
import fsInjectable from "../fs.injectable";
|
import fsInjectable from "../fs.injectable";
|
||||||
|
|
||||||
|
export type Stat = (path: string) => Promise<Stats>;
|
||||||
|
|
||||||
const statInjectable = getInjectable({
|
const statInjectable = getInjectable({
|
||||||
id: "stat",
|
id: "stat",
|
||||||
|
instantiate: (di): Stat => di.inject(fsInjectable).stat,
|
||||||
instantiate: (di) => di.inject(fsInjectable).stat,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default statInjectable;
|
export default statInjectable;
|
||||||
|
|||||||
@ -3,15 +3,161 @@
|
|||||||
* 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 type { FSWatcher, WatchOptions } from "chokidar";
|
|
||||||
import { watch } from "chokidar";
|
import { watch } from "chokidar";
|
||||||
|
import type { Stats } from "fs";
|
||||||
|
import type TypedEventEmitter from "typed-emitter";
|
||||||
|
import type { SingleOrMany } from "../../utils";
|
||||||
|
|
||||||
export type Watch = (path: string, options?: WatchOptions) => FSWatcher;
|
export interface AlwaysStatWatcherEvents {
|
||||||
|
add: (path: string, stats: Stats) => void;
|
||||||
|
addDir: (path: string, stats: Stats) => void;
|
||||||
|
change: (path: string, stats: Stats) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MaybeStatWatcherEvents {
|
||||||
|
add: (path: string, stats?: Stats) => void;
|
||||||
|
addDir: (path: string, stats?: Stats) => void;
|
||||||
|
change: (path: string, stats?: Stats) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type WatcherEvents<AlwaysStat extends boolean> = BaseWatcherEvents
|
||||||
|
& (
|
||||||
|
AlwaysStat extends true
|
||||||
|
? AlwaysStatWatcherEvents
|
||||||
|
: MaybeStatWatcherEvents
|
||||||
|
);
|
||||||
|
|
||||||
|
export interface BaseWatcherEvents {
|
||||||
|
error: (error: Error) => void;
|
||||||
|
ready: () => void;
|
||||||
|
unlink: (path: string) => void;
|
||||||
|
unlinkDir: (path: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Watcher<AlwaysStat extends boolean> extends TypedEventEmitter<WatcherEvents<AlwaysStat>> {
|
||||||
|
close: () => Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type WatcherOptions<AlwaysStat extends boolean> = {
|
||||||
|
/**
|
||||||
|
* Indicates whether the process should continue to run as long as files are being watched. If
|
||||||
|
* set to `false` when using `fsevents` to watch, no more events will be emitted after `ready`,
|
||||||
|
* even if the process continues to run.
|
||||||
|
*/
|
||||||
|
persistent?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ([anymatch](https://github.com/micromatch/anymatch)-compatible definition) Defines files/paths to
|
||||||
|
* be ignored. The whole relative or absolute path is tested, not just filename. If a function
|
||||||
|
* with two arguments is provided, it gets called twice per path - once with a single argument
|
||||||
|
* (the path), second time with two arguments (the path and the
|
||||||
|
* [`fs.Stats`](https://nodejs.org/api/fs.html#fs_class_fs_stats) object of that path).
|
||||||
|
*/
|
||||||
|
ignored?: SingleOrMany<string | RegExp | ((path: string) => boolean)>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If set to `false` then `add`/`addDir` events are also emitted for matching paths while
|
||||||
|
* instantiating the watching as chokidar discovers these file paths (before the `ready` event).
|
||||||
|
*/
|
||||||
|
ignoreInitial?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When `false`, only the symlinks themselves will be watched for changes instead of following
|
||||||
|
* the link references and bubbling events through the link's path.
|
||||||
|
*/
|
||||||
|
followSymlinks?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The base directory from which watch `paths` are to be derived. Paths emitted with events will
|
||||||
|
* be relative to this.
|
||||||
|
*/
|
||||||
|
cwd?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If set to true then the strings passed to .watch() and .add() are treated as literal path
|
||||||
|
* names, even if they look like globs. Default: false.
|
||||||
|
*/
|
||||||
|
disableGlobbing?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to use fs.watchFile (backed by polling), or fs.watch. If polling leads to high CPU
|
||||||
|
* utilization, consider setting this to `false`. It is typically necessary to **set this to
|
||||||
|
* `true` to successfully watch files over a network**, and it may be necessary to successfully
|
||||||
|
* watch files in other non-standard situations. Setting to `true` explicitly on OS X overrides
|
||||||
|
* the `useFsEvents` default.
|
||||||
|
*/
|
||||||
|
usePolling?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to use the `fsevents` watching interface if available. When set to `true` explicitly
|
||||||
|
* and `fsevents` is available this supercedes the `usePolling` setting. When set to `false` on
|
||||||
|
* OS X, `usePolling: true` becomes the default.
|
||||||
|
*/
|
||||||
|
useFsEvents?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If set, limits how many levels of subdirectories will be traversed.
|
||||||
|
*/
|
||||||
|
depth?: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interval of file system polling.
|
||||||
|
*/
|
||||||
|
interval?: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interval of file system polling for binary files. ([see list of binary extensions](https://gi
|
||||||
|
* thub.com/sindresorhus/binary-extensions/blob/master/binary-extensions.json))
|
||||||
|
*/
|
||||||
|
binaryInterval?: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates whether to watch files that don't have read permissions if possible. If watching
|
||||||
|
* fails due to `EPERM` or `EACCES` with this set to `true`, the errors will be suppressed
|
||||||
|
* silently.
|
||||||
|
*/
|
||||||
|
ignorePermissionErrors?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `true` if `useFsEvents` and `usePolling` are `false`). Automatically filters out artifacts
|
||||||
|
* that occur when using editors that use "atomic writes" instead of writing directly to the
|
||||||
|
* source file. If a file is re-added within 100 ms of being deleted, Chokidar emits a `change`
|
||||||
|
* event rather than `unlink` then `add`. If the default of 100 ms does not work well for you,
|
||||||
|
* you can override it by setting `atomic` to a custom value, in milliseconds.
|
||||||
|
*/
|
||||||
|
atomic?: boolean | number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* can be set to an object in order to adjust timing params:
|
||||||
|
*/
|
||||||
|
awaitWriteFinish?: AwaitWriteFinishOptions | boolean;
|
||||||
|
} & (AlwaysStat extends true
|
||||||
|
? {
|
||||||
|
alwaysStat: true;
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
alwaysStat?: false;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export interface AwaitWriteFinishOptions {
|
||||||
|
/**
|
||||||
|
* Amount of time in milliseconds for a file size to remain constant before emitting its event.
|
||||||
|
*/
|
||||||
|
stabilityThreshold?: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* File size polling interval.
|
||||||
|
*/
|
||||||
|
pollInterval?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Watch = <AlwaysStat extends boolean = false>(path: string, options?: WatcherOptions<AlwaysStat>) => Watcher<AlwaysStat>;
|
||||||
|
|
||||||
// TODO: Introduce wrapper to allow simpler API
|
// TODO: Introduce wrapper to allow simpler API
|
||||||
const watchInjectable = getInjectable({
|
const watchInjectable = getInjectable({
|
||||||
id: "watch",
|
id: "watch",
|
||||||
instantiate: (): Watch => watch,
|
instantiate: () => watch as Watch,
|
||||||
causesSideEffects: true,
|
causesSideEffects: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
37
src/common/logger/prefixed-logger.injectable.ts
Normal file
37
src/common/logger/prefixed-logger.injectable.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import type { Logger } from "../logger";
|
||||||
|
import loggerInjectable from "../logger.injectable";
|
||||||
|
|
||||||
|
const prefixedLoggerInjectable = getInjectable({
|
||||||
|
id: "prefixed-logger",
|
||||||
|
instantiate: (di, prefix): Logger => {
|
||||||
|
const logger = di.inject(loggerInjectable);
|
||||||
|
|
||||||
|
return {
|
||||||
|
debug: (message, ...args) => {
|
||||||
|
logger.debug(`[${prefix}]: ${message}`, ...args);
|
||||||
|
},
|
||||||
|
error: (message, ...args) => {
|
||||||
|
logger.error(`[${prefix}]: ${message}`, ...args);
|
||||||
|
},
|
||||||
|
info: (message, ...args) => {
|
||||||
|
logger.info(`[${prefix}]: ${message}`, ...args);
|
||||||
|
},
|
||||||
|
silly: (message, ...args) => {
|
||||||
|
logger.silly(`[${prefix}]: ${message}`, ...args);
|
||||||
|
},
|
||||||
|
warn: (message, ...args) => {
|
||||||
|
logger.warn(`[${prefix}]: ${message}`, ...args);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
lifecycle: lifecycleEnum.keyedSingleton({
|
||||||
|
getInstanceKey: (di, prefix: string) => prefix,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default prefixedLoggerInjectable;
|
||||||
@ -25,13 +25,19 @@ describe("runManyFor", () => {
|
|||||||
|
|
||||||
const someInjectable = getInjectable({
|
const someInjectable = getInjectable({
|
||||||
id: "some-injectable",
|
id: "some-injectable",
|
||||||
instantiate: () => ({ run: () => runMock("some-call") }),
|
instantiate: () => ({
|
||||||
|
id: "some-injectable",
|
||||||
|
run: () => runMock("some-call"),
|
||||||
|
}),
|
||||||
injectionToken: someInjectionTokenForRunnables,
|
injectionToken: someInjectionTokenForRunnables,
|
||||||
});
|
});
|
||||||
|
|
||||||
const someOtherInjectable = getInjectable({
|
const someOtherInjectable = getInjectable({
|
||||||
id: "some-other-injectable",
|
id: "some-other-injectable",
|
||||||
instantiate: () => ({ run: () => runMock("some-other-call") }),
|
instantiate: () => ({
|
||||||
|
id: "some-other-injectable",
|
||||||
|
run: () => runMock("some-other-call"),
|
||||||
|
}),
|
||||||
injectionToken: someInjectionTokenForRunnables,
|
injectionToken: someInjectionTokenForRunnables,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -79,6 +85,7 @@ describe("runManyFor", () => {
|
|||||||
id: "some-injectable-1",
|
id: "some-injectable-1",
|
||||||
|
|
||||||
instantiate: (di) => ({
|
instantiate: (di) => ({
|
||||||
|
id: "some-injectable-1",
|
||||||
run: () => runMock("third-level-run"),
|
run: () => runMock("third-level-run"),
|
||||||
runAfter: di.inject(someInjectable2),
|
runAfter: di.inject(someInjectable2),
|
||||||
}),
|
}),
|
||||||
@ -90,6 +97,7 @@ describe("runManyFor", () => {
|
|||||||
id: "some-injectable-2",
|
id: "some-injectable-2",
|
||||||
|
|
||||||
instantiate: (di) => ({
|
instantiate: (di) => ({
|
||||||
|
id: "some-injectable-2",
|
||||||
run: () => runMock("second-level-run"),
|
run: () => runMock("second-level-run"),
|
||||||
runAfter: di.inject(someInjectable3),
|
runAfter: di.inject(someInjectable3),
|
||||||
}),
|
}),
|
||||||
@ -99,7 +107,10 @@ describe("runManyFor", () => {
|
|||||||
|
|
||||||
const someInjectable3 = getInjectable({
|
const someInjectable3 = getInjectable({
|
||||||
id: "some-injectable-3",
|
id: "some-injectable-3",
|
||||||
instantiate: () => ({ run: () => runMock("first-level-run") }),
|
instantiate: () => ({
|
||||||
|
id: "some-injectable-3",
|
||||||
|
run: () => runMock("first-level-run"),
|
||||||
|
}),
|
||||||
injectionToken: someInjectionTokenForRunnables,
|
injectionToken: someInjectionTokenForRunnables,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -186,6 +197,7 @@ describe("runManyFor", () => {
|
|||||||
id: "some-runnable-1",
|
id: "some-runnable-1",
|
||||||
|
|
||||||
instantiate: (di) => ({
|
instantiate: (di) => ({
|
||||||
|
id: "some-runnable-1",
|
||||||
run: () => runMock("some-runnable-1"),
|
run: () => runMock("some-runnable-1"),
|
||||||
runAfter: di.inject(someOtherInjectable),
|
runAfter: di.inject(someOtherInjectable),
|
||||||
}),
|
}),
|
||||||
@ -197,6 +209,7 @@ describe("runManyFor", () => {
|
|||||||
id: "some-runnable-2",
|
id: "some-runnable-2",
|
||||||
|
|
||||||
instantiate: () => ({
|
instantiate: () => ({
|
||||||
|
id: "some-runnable-2",
|
||||||
run: () => runMock("some-runnable-2"),
|
run: () => runMock("some-runnable-2"),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@ -210,7 +223,7 @@ describe("runManyFor", () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return expect(() => runMany()).rejects.toThrow(
|
return expect(() => runMany()).rejects.toThrow(
|
||||||
"Tried to run runnable after other runnable which does not same injection token.",
|
'Tried to run runnable "some-runnable-1" after the runnable "some-runnable-2" which does not share the "some-injection-token" injection token.',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -232,6 +245,7 @@ describe("runManyFor", () => {
|
|||||||
id: "some-runnable-1",
|
id: "some-runnable-1",
|
||||||
|
|
||||||
instantiate: () => ({
|
instantiate: () => ({
|
||||||
|
id: "some-runnable-1",
|
||||||
run: (parameter) => runMock("run-of-some-runnable-1", parameter),
|
run: (parameter) => runMock("run-of-some-runnable-1", parameter),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@ -242,6 +256,7 @@ describe("runManyFor", () => {
|
|||||||
id: "some-runnable-2",
|
id: "some-runnable-2",
|
||||||
|
|
||||||
instantiate: () => ({
|
instantiate: () => ({
|
||||||
|
id: "some-runnable-2",
|
||||||
run: (parameter) => runMock("run-of-some-runnable-2", parameter),
|
run: (parameter) => runMock("run-of-some-runnable-2", parameter),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
|||||||
@ -11,8 +11,9 @@ import { filter, forEach, map, tap } from "lodash/fp";
|
|||||||
import { throwWithIncorrectHierarchyFor } from "./throw-with-incorrect-hierarchy-for";
|
import { throwWithIncorrectHierarchyFor } from "./throw-with-incorrect-hierarchy-for";
|
||||||
|
|
||||||
export interface Runnable<TParameter = void> {
|
export interface Runnable<TParameter = void> {
|
||||||
|
id: string;
|
||||||
run: Run<TParameter>;
|
run: Run<TParameter>;
|
||||||
runAfter?: this;
|
runAfter?: Runnable<TParameter>;
|
||||||
}
|
}
|
||||||
|
|
||||||
type Run<Param> = (parameter: Param) => Promise<void> | void;
|
type Run<Param> = (parameter: Param) => Promise<void> | void;
|
||||||
@ -25,7 +26,7 @@ export function runManyFor(di: DiContainerForInjection): RunMany {
|
|||||||
return (injectionToken) => async (parameter) => {
|
return (injectionToken) => async (parameter) => {
|
||||||
const allRunnables = di.injectMany(injectionToken);
|
const allRunnables = di.injectMany(injectionToken);
|
||||||
|
|
||||||
const throwWithIncorrectHierarchy = throwWithIncorrectHierarchyFor(allRunnables);
|
const throwWithIncorrectHierarchy = throwWithIncorrectHierarchyFor((injectionToken as any).id, allRunnables);
|
||||||
|
|
||||||
const recursedRun = async (
|
const recursedRun = async (
|
||||||
runAfterRunnable: Runnable<any> | undefined = undefined,
|
runAfterRunnable: Runnable<any> | undefined = undefined,
|
||||||
|
|||||||
@ -21,13 +21,19 @@ describe("runManySyncFor", () => {
|
|||||||
|
|
||||||
const someInjectable = getInjectable({
|
const someInjectable = getInjectable({
|
||||||
id: "some-injectable",
|
id: "some-injectable",
|
||||||
instantiate: () => ({ run: () => runMock("some-call") }),
|
instantiate: () => ({
|
||||||
|
id: "some-injectable",
|
||||||
|
run: () => runMock("some-call"),
|
||||||
|
}),
|
||||||
injectionToken: someInjectionTokenForRunnables,
|
injectionToken: someInjectionTokenForRunnables,
|
||||||
});
|
});
|
||||||
|
|
||||||
const someOtherInjectable = getInjectable({
|
const someOtherInjectable = getInjectable({
|
||||||
id: "some-other-injectable",
|
id: "some-other-injectable",
|
||||||
instantiate: () => ({ run: () => runMock("some-other-call") }),
|
instantiate: () => ({
|
||||||
|
id: "some-other-injectable",
|
||||||
|
run: () => runMock("some-other-call"),
|
||||||
|
}),
|
||||||
injectionToken: someInjectionTokenForRunnables,
|
injectionToken: someInjectionTokenForRunnables,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -62,6 +68,7 @@ describe("runManySyncFor", () => {
|
|||||||
id: "some-injectable-1",
|
id: "some-injectable-1",
|
||||||
|
|
||||||
instantiate: (di) => ({
|
instantiate: (di) => ({
|
||||||
|
id: "some-injectable-1",
|
||||||
run: () => runMock("third-level-run"),
|
run: () => runMock("third-level-run"),
|
||||||
runAfter: di.inject(someInjectable2),
|
runAfter: di.inject(someInjectable2),
|
||||||
}),
|
}),
|
||||||
@ -73,6 +80,7 @@ describe("runManySyncFor", () => {
|
|||||||
id: "some-injectable-2",
|
id: "some-injectable-2",
|
||||||
|
|
||||||
instantiate: (di) => ({
|
instantiate: (di) => ({
|
||||||
|
id: "some-injectable-2",
|
||||||
run: () => runMock("second-level-run"),
|
run: () => runMock("second-level-run"),
|
||||||
runAfter: di.inject(someInjectable3),
|
runAfter: di.inject(someInjectable3),
|
||||||
}),
|
}),
|
||||||
@ -82,7 +90,10 @@ describe("runManySyncFor", () => {
|
|||||||
|
|
||||||
const someInjectable3 = getInjectable({
|
const someInjectable3 = getInjectable({
|
||||||
id: "some-injectable-3",
|
id: "some-injectable-3",
|
||||||
instantiate: () => ({ run: () => runMock("first-level-run") }),
|
instantiate: () => ({
|
||||||
|
id: "some-injectable-3",
|
||||||
|
run: () => runMock("first-level-run"),
|
||||||
|
}),
|
||||||
injectionToken: someInjectionTokenForRunnables,
|
injectionToken: someInjectionTokenForRunnables,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -115,6 +126,7 @@ describe("runManySyncFor", () => {
|
|||||||
id: "some-runnable-1",
|
id: "some-runnable-1",
|
||||||
|
|
||||||
instantiate: (di) => ({
|
instantiate: (di) => ({
|
||||||
|
id: "some-runnable-1",
|
||||||
run: () => runMock("some-runnable-1"),
|
run: () => runMock("some-runnable-1"),
|
||||||
runAfter: di.inject(someOtherInjectable),
|
runAfter: di.inject(someOtherInjectable),
|
||||||
}),
|
}),
|
||||||
@ -126,6 +138,7 @@ describe("runManySyncFor", () => {
|
|||||||
id: "some-runnable-2",
|
id: "some-runnable-2",
|
||||||
|
|
||||||
instantiate: () => ({
|
instantiate: () => ({
|
||||||
|
id: "some-runnable-2",
|
||||||
run: () => runMock("some-runnable-2"),
|
run: () => runMock("some-runnable-2"),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@ -139,7 +152,7 @@ describe("runManySyncFor", () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return expect(() => runMany()).rejects.toThrow(
|
return expect(() => runMany()).rejects.toThrow(
|
||||||
"Tried to run runnable after other runnable which does not same injection token.",
|
'Tried to run runnable "some-runnable-1" after the runnable "some-runnable-2" which does not share the "some-injection-token" injection token.',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -161,6 +174,7 @@ describe("runManySyncFor", () => {
|
|||||||
id: "some-runnable-1",
|
id: "some-runnable-1",
|
||||||
|
|
||||||
instantiate: () => ({
|
instantiate: () => ({
|
||||||
|
id: "some-runnable-1",
|
||||||
run: (parameter) => runMock("run-of-some-runnable-1", parameter),
|
run: (parameter) => runMock("run-of-some-runnable-1", parameter),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@ -171,6 +185,7 @@ describe("runManySyncFor", () => {
|
|||||||
id: "some-runnable-2",
|
id: "some-runnable-2",
|
||||||
|
|
||||||
instantiate: () => ({
|
instantiate: () => ({
|
||||||
|
id: "some-runnable-2",
|
||||||
run: (parameter) => runMock("run-of-some-runnable-2", parameter),
|
run: (parameter) => runMock("run-of-some-runnable-2", parameter),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
|||||||
@ -3,17 +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 { pipeline } from "@ogre-tools/fp";
|
import { pipeline } from "@ogre-tools/fp";
|
||||||
import type {
|
import type { DiContainerForInjection, InjectionToken } from "@ogre-tools/injectable";
|
||||||
DiContainerForInjection,
|
|
||||||
InjectionToken,
|
|
||||||
} from "@ogre-tools/injectable";
|
|
||||||
import { filter, forEach, map, tap } from "lodash/fp";
|
import { filter, forEach, map, tap } from "lodash/fp";
|
||||||
import type { Runnable } from "./run-many-for";
|
import type { Runnable } from "./run-many-for";
|
||||||
import { throwWithIncorrectHierarchyFor } from "./throw-with-incorrect-hierarchy-for";
|
import { throwWithIncorrectHierarchyFor } from "./throw-with-incorrect-hierarchy-for";
|
||||||
|
|
||||||
export interface RunnableSync<TParameter = void> {
|
export interface RunnableSync<TParameter = void> {
|
||||||
|
id: string;
|
||||||
run: RunSync<TParameter>;
|
run: RunSync<TParameter>;
|
||||||
runAfter?: this;
|
runAfter?: RunnableSync<TParameter>;
|
||||||
}
|
}
|
||||||
|
|
||||||
type RunSync<Param> = (parameter: Param) => void;
|
type RunSync<Param> = (parameter: Param) => void;
|
||||||
@ -26,7 +24,7 @@ export function runManySyncFor(di: DiContainerForInjection): RunManySync {
|
|||||||
return (injectionToken) => async (parameter) => {
|
return (injectionToken) => async (parameter) => {
|
||||||
const allRunnables = di.injectMany(injectionToken);
|
const allRunnables = di.injectMany(injectionToken);
|
||||||
|
|
||||||
const throwWithIncorrectHierarchy = throwWithIncorrectHierarchyFor(allRunnables);
|
const throwWithIncorrectHierarchy = throwWithIncorrectHierarchyFor((injectionToken as any).id, allRunnables);
|
||||||
|
|
||||||
const recursedRun = (
|
const recursedRun = (
|
||||||
runAfterRunnable: RunnableSync<any> | undefined = undefined,
|
runAfterRunnable: RunnableSync<any> | undefined = undefined,
|
||||||
|
|||||||
@ -5,12 +5,10 @@
|
|||||||
import type { Runnable } from "./run-many-for";
|
import type { Runnable } from "./run-many-for";
|
||||||
import type { RunnableSync } from "./run-many-sync-for";
|
import type { RunnableSync } from "./run-many-sync-for";
|
||||||
|
|
||||||
export const throwWithIncorrectHierarchyFor =
|
export const throwWithIncorrectHierarchyFor = (injectionTokenId: string, allRunnables: Runnable<any>[] | RunnableSync<any>[]) => (
|
||||||
(allRunnables: Runnable<any>[] | RunnableSync<any>[]) =>
|
(runnable: Runnable<any> | RunnableSync<any>) => {
|
||||||
(runnable: Runnable<any> | RunnableSync<any>) => {
|
if (runnable.runAfter && !allRunnables.includes(runnable.runAfter)) {
|
||||||
if (runnable.runAfter && !allRunnables.includes(runnable.runAfter)) {
|
throw new Error(`Tried to run runnable "${runnable.id}" after the runnable "${runnable.runAfter.id}" which does not share the "${injectionTokenId}" injection token.`);
|
||||||
throw new Error(
|
}
|
||||||
"Tried to run runnable after other runnable which does not same injection token.",
|
}
|
||||||
);
|
);
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|||||||
17
src/common/user-store/kubeconfig-syncs.injectable.ts
Normal file
17
src/common/user-store/kubeconfig-syncs.injectable.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
/**
|
||||||
|
* 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 userStoreInjectable from "./user-store.injectable";
|
||||||
|
|
||||||
|
const kubeconfigSyncsInjectable = getInjectable({
|
||||||
|
id: "kubeconfig-syncs",
|
||||||
|
instantiate: (di) => {
|
||||||
|
const store = di.inject(userStoreInjectable);
|
||||||
|
|
||||||
|
return store.syncKubeconfigEntries;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default kubeconfigSyncsInjectable;
|
||||||
@ -11,6 +11,7 @@ interface Iterator<T> {
|
|||||||
find(fn: (val: T) => unknown): T | undefined;
|
find(fn: (val: T) => unknown): T | undefined;
|
||||||
collect<U>(fn: (values: Iterable<T>) => U): U;
|
collect<U>(fn: (values: Iterable<T>) => U): U;
|
||||||
map<U>(fn: (val: T) => U): Iterator<U>;
|
map<U>(fn: (val: T) => U): Iterator<U>;
|
||||||
|
flatMap<U>(fn: (val: T) => U[]): Iterator<U>;
|
||||||
join(sep?: string): string;
|
join(sep?: string): string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -19,6 +20,7 @@ export function pipeline<T>(src: IterableIterator<T>): Iterator<T> {
|
|||||||
filter: (fn) => pipeline(filter(src, fn)),
|
filter: (fn) => pipeline(filter(src, fn)),
|
||||||
filterMap: (fn) => pipeline(filterMap(src, fn)),
|
filterMap: (fn) => pipeline(filterMap(src, fn)),
|
||||||
map: (fn) => pipeline(map(src, fn)),
|
map: (fn) => pipeline(map(src, fn)),
|
||||||
|
flatMap: (fn) => pipeline(flatMap(src, fn)),
|
||||||
find: (fn) => find(src, fn),
|
find: (fn) => find(src, fn),
|
||||||
join: (sep) => join(src, sep),
|
join: (sep) => join(src, sep),
|
||||||
collect: (fn) => fn(src),
|
collect: (fn) => fn(src),
|
||||||
|
|||||||
@ -120,7 +120,7 @@ export const apiKubePrefix = "/api-kube"; // k8s cluster apis
|
|||||||
// Links
|
// Links
|
||||||
export const issuesTrackerUrl = "https://github.com/lensapp/lens/issues" as string;
|
export const issuesTrackerUrl = "https://github.com/lensapp/lens/issues" as string;
|
||||||
export const slackUrl = "https://join.slack.com/t/k8slens/shared_invite/zt-wcl8jq3k-68R5Wcmk1o95MLBE5igUDQ" as string;
|
export const slackUrl = "https://join.slack.com/t/k8slens/shared_invite/zt-wcl8jq3k-68R5Wcmk1o95MLBE5igUDQ" as string;
|
||||||
export const supportUrl = "https://docs.k8slens.dev/latest/support/" as string;
|
export const supportUrl = "https://docs.k8slens.dev/support/" as string;
|
||||||
|
|
||||||
export const lensWebsiteWeblinkId = "lens-website-link";
|
export const lensWebsiteWeblinkId = "lens-website-link";
|
||||||
export const lensDocumentationWeblinkId = "lens-documentation-link";
|
export const lensDocumentationWeblinkId = "lens-documentation-link";
|
||||||
@ -129,4 +129,4 @@ export const lensTwitterWeblinkId = "lens-twitter-link";
|
|||||||
export const lensBlogWeblinkId = "lens-blog-link";
|
export const lensBlogWeblinkId = "lens-blog-link";
|
||||||
export const kubernetesDocumentationWeblinkId = "kubernetes-documentation-link";
|
export const kubernetesDocumentationWeblinkId = "kubernetes-documentation-link";
|
||||||
|
|
||||||
export const docsUrl = "https://docs.k8slens.dev/main" as string;
|
export const docsUrl = "https://docs.k8slens.dev" as string;
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import { getGlobalOverride } from "../test-utils/get-global-override";
|
|||||||
import applicationInformationInjectable from "./application-information.injectable";
|
import applicationInformationInjectable from "./application-information.injectable";
|
||||||
|
|
||||||
export default getGlobalOverride(applicationInformationInjectable, () => ({
|
export default getGlobalOverride(applicationInformationInjectable, () => ({
|
||||||
|
name: "some-product-name",
|
||||||
productName: "some-product-name",
|
productName: "some-product-name",
|
||||||
version: "6.0.0",
|
version: "6.0.0",
|
||||||
build: {},
|
build: {},
|
||||||
|
|||||||
@ -5,16 +5,16 @@
|
|||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import packageJson from "../../../package.json";
|
import packageJson from "../../../package.json";
|
||||||
|
|
||||||
export type ApplicationInformation = Pick<typeof packageJson, "version" | "config" | "productName" | "copyright" | "description"> & {
|
export type ApplicationInformation = Pick<typeof packageJson, "version" | "config" | "productName" | "copyright" | "description" | "name"> & {
|
||||||
build: Partial<typeof packageJson["build"]> & { publish?: unknown[] };
|
build: Partial<typeof packageJson["build"]> & { publish?: unknown[] };
|
||||||
};
|
};
|
||||||
|
|
||||||
const applicationInformationInjectable = getInjectable({
|
const applicationInformationInjectable = getInjectable({
|
||||||
id: "application-information",
|
id: "application-information",
|
||||||
instantiate: (): ApplicationInformation => {
|
instantiate: (): ApplicationInformation => {
|
||||||
const { version, config, productName, build, copyright, description } = packageJson;
|
const { version, config, productName, build, copyright, description, name } = packageJson;
|
||||||
|
|
||||||
return { version, config, productName, build, copyright, description };
|
return { version, config, productName, build, copyright, description, name };
|
||||||
},
|
},
|
||||||
causesSideEffects: true,
|
causesSideEffects: true,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -278,7 +278,7 @@ exports[`add-cluster - navigation using application menu when navigating to add
|
|||||||
</code>
|
</code>
|
||||||
file.
|
file.
|
||||||
<a
|
<a
|
||||||
href="https://docs.k8slens.dev/main/getting-started/add-cluster/"
|
href="https://docs.k8slens.dev/getting-started/add-cluster/"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
|
|||||||
@ -274,7 +274,7 @@ exports[`extensions - navigation using application menu when navigating to exten
|
|||||||
<p>
|
<p>
|
||||||
Add new features via Lens Extensions. Check out the
|
Add new features via Lens Extensions. Check out the
|
||||||
<a
|
<a
|
||||||
href="https://docs.k8slens.dev/main/extensions/"
|
href="https://docs.k8slens.dev/extensions/"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
|
|||||||
@ -5,9 +5,9 @@
|
|||||||
|
|
||||||
import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
|
import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
|
||||||
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
|
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
|
||||||
import type { ClusterManager } from "../../main/cluster-manager";
|
import type { ClusterManager } from "../../main/cluster/manager";
|
||||||
import exitAppInjectable from "../../main/electron-app/features/exit-app.injectable";
|
import exitAppInjectable from "../../main/electron-app/features/exit-app.injectable";
|
||||||
import clusterManagerInjectable from "../../main/cluster-manager.injectable";
|
import clusterManagerInjectable from "../../main/cluster/manager.injectable";
|
||||||
import stopServicesAndExitAppInjectable from "../../main/stop-services-and-exit-app.injectable";
|
import stopServicesAndExitAppInjectable from "../../main/stop-services-and-exit-app.injectable";
|
||||||
import { advanceFakeTime, useFakeTime } from "../../common/test-utils/use-fake-time";
|
import { advanceFakeTime, useFakeTime } from "../../common/test-utils/use-fake-time";
|
||||||
|
|
||||||
|
|||||||
@ -27,6 +27,7 @@ const setupAppPathsInjectable = getInjectable({
|
|||||||
const joinPaths = di.inject(joinPathsInjectable);
|
const joinPaths = di.inject(joinPathsInjectable);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
id: "setup-app-paths",
|
||||||
run: () => {
|
run: () => {
|
||||||
if (directoryForIntegrationTesting) {
|
if (directoryForIntegrationTesting) {
|
||||||
setElectronAppPath("appData", directoryForIntegrationTesting);
|
setElectronAppPath("appData", directoryForIntegrationTesting);
|
||||||
|
|||||||
@ -16,6 +16,7 @@ const emitCurrentVersionToAnalyticsInjectable = getInjectable({
|
|||||||
const buildVersion = di.inject(buildVersionInjectable);
|
const buildVersion = di.inject(buildVersionInjectable);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
id: "emit-current-version-to-analytics",
|
||||||
run: () => {
|
run: () => {
|
||||||
emitEvent({
|
emitEvent({
|
||||||
name: "app",
|
name: "app",
|
||||||
|
|||||||
@ -15,6 +15,7 @@ const startCheckingForUpdatesInjectable = getInjectable({
|
|||||||
const updatingIsEnabled = di.inject(updatingIsEnabledInjectable);
|
const updatingIsEnabled = di.inject(updatingIsEnabledInjectable);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
id: "start-checking-for-updates",
|
||||||
run: async () => {
|
run: async () => {
|
||||||
if (updatingIsEnabled && !periodicalCheckForUpdates.started) {
|
if (updatingIsEnabled && !periodicalCheckForUpdates.started) {
|
||||||
await periodicalCheckForUpdates.start();
|
await periodicalCheckForUpdates.start();
|
||||||
|
|||||||
@ -13,6 +13,7 @@ const stopCheckingForUpdatesInjectable = getInjectable({
|
|||||||
const periodicalCheckForUpdates = di.inject(periodicalCheckForUpdatesInjectable);
|
const periodicalCheckForUpdates = di.inject(periodicalCheckForUpdatesInjectable);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
id: "stop-checking-for-updates",
|
||||||
run: async () => {
|
run: async () => {
|
||||||
if (periodicalCheckForUpdates.started) {
|
if (periodicalCheckForUpdates.started) {
|
||||||
await periodicalCheckForUpdates.stop();
|
await periodicalCheckForUpdates.stop();
|
||||||
|
|||||||
@ -13,6 +13,7 @@ const startWatchingIfUpdateShouldHappenOnQuitInjectable = getInjectable({
|
|||||||
const watchIfUpdateShouldHappenOnQuit = di.inject(watchIfUpdateShouldHappenOnQuitInjectable);
|
const watchIfUpdateShouldHappenOnQuit = di.inject(watchIfUpdateShouldHappenOnQuitInjectable);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
id: "start-watching-if-update-should-happen-on-quit",
|
||||||
run: () => {
|
run: () => {
|
||||||
watchIfUpdateShouldHappenOnQuit.start();
|
watchIfUpdateShouldHappenOnQuit.start();
|
||||||
},
|
},
|
||||||
|
|||||||
@ -13,6 +13,7 @@ const stopWatchingIfUpdateShouldHappenOnQuitInjectable = getInjectable({
|
|||||||
const watchIfUpdateShouldHappenOnQuit = di.inject(watchIfUpdateShouldHappenOnQuitInjectable);
|
const watchIfUpdateShouldHappenOnQuit = di.inject(watchIfUpdateShouldHappenOnQuitInjectable);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
id: "stop-watching-if-update-should-happen-on-quit",
|
||||||
run: () => {
|
run: () => {
|
||||||
watchIfUpdateShouldHappenOnQuit.stop();
|
watchIfUpdateShouldHappenOnQuit.stop();
|
||||||
},
|
},
|
||||||
|
|||||||
@ -3,71 +3,56 @@
|
|||||||
* 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 { ObservableMap } from "mobx";
|
import { observable, ObservableMap } from "mobx";
|
||||||
import type { CatalogEntity } from "../../../common/catalog";
|
import type { CatalogEntity } from "../../../common/catalog";
|
||||||
import { loadFromOptions } from "../../../common/kube-helpers";
|
import { loadFromOptions } from "../../../common/kube-helpers";
|
||||||
import type { Cluster } from "../../../common/cluster/cluster";
|
import type { Cluster } from "../../../common/cluster/cluster";
|
||||||
import { computeDiff as computeDiffFor, configToModels } from "../kubeconfig-sync/manager";
|
|
||||||
import mockFs from "mock-fs";
|
|
||||||
import fs from "fs";
|
|
||||||
import clusterStoreInjectable from "../../../common/cluster-store/cluster-store.injectable";
|
|
||||||
import { getDiForUnitTesting } from "../../getDiForUnitTesting";
|
import { getDiForUnitTesting } from "../../getDiForUnitTesting";
|
||||||
import { createClusterInjectionToken } from "../../../common/cluster/create-cluster-injection-token";
|
|
||||||
import directoryForKubeConfigsInjectable from "../../../common/app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable";
|
|
||||||
import getConfigurationFileModelInjectable from "../../../common/get-configuration-file-model/get-configuration-file-model.injectable";
|
|
||||||
import clusterManagerInjectable from "../../cluster-manager.injectable";
|
|
||||||
import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||||
import directoryForTempInjectable from "../../../common/app-paths/directory-for-temp/directory-for-temp.injectable";
|
import directoryForTempInjectable from "../../../common/app-paths/directory-for-temp/directory-for-temp.injectable";
|
||||||
import kubectlBinaryNameInjectable from "../../kubectl/binary-name.injectable";
|
import { iter, strictGet } from "../../../common/utils";
|
||||||
import kubectlDownloadingNormalizedArchInjectable from "../../kubectl/normalized-arch.injectable";
|
import type { ComputeKubeconfigDiff } from "../kubeconfig-sync/compute-diff.injectable";
|
||||||
import normalizedPlatformInjectable from "../../../common/vars/normalized-platform.injectable";
|
import computeKubeconfigDiffInjectable from "../kubeconfig-sync/compute-diff.injectable";
|
||||||
import { iter } from "../../../common/utils";
|
import type { ConfigToModels } from "../kubeconfig-sync/config-to-models.injectable";
|
||||||
import fsInjectable from "../../../common/fs/fs.injectable";
|
import configToModelsInjectable from "../kubeconfig-sync/config-to-models.injectable";
|
||||||
|
import kubeconfigSyncManagerInjectable from "../kubeconfig-sync/manager.injectable";
|
||||||
jest.mock("electron", () => ({
|
import type { KubeconfigSyncManager } from "../kubeconfig-sync/manager";
|
||||||
app: {
|
import type { KubeconfigSyncValue } from "../../../common/user-store";
|
||||||
getVersion: () => "99.99.99",
|
import kubeconfigSyncsInjectable from "../../../common/user-store/kubeconfig-syncs.injectable";
|
||||||
getName: () => "lens",
|
import getClusterByIdInjectable from "../../../common/cluster-store/get-by-id.injectable";
|
||||||
setName: jest.fn(),
|
import type { DiContainer } from "@ogre-tools/injectable";
|
||||||
setPath: jest.fn(),
|
import type { AsyncFnMock } from "@async-fn/jest";
|
||||||
getPath: () => "tmp",
|
import type { Stat } from "../../../common/fs/stat/stat.injectable";
|
||||||
getLocale: () => "en",
|
import asyncFn from "@async-fn/jest";
|
||||||
setLoginItemSettings: jest.fn(),
|
import statInjectable from "../../../common/fs/stat/stat.injectable";
|
||||||
},
|
import type { Watcher } from "../../../common/fs/watch/watch.injectable";
|
||||||
ipcMain: {
|
import watchInjectable from "../../../common/fs/watch/watch.injectable";
|
||||||
on: jest.fn(),
|
import EventEmitter from "events";
|
||||||
handle: jest.fn(),
|
import type { ReadStream, Stats } from "fs";
|
||||||
},
|
import createReadFileStreamInjectable from "../../../common/fs/create-read-file-stream.injectable";
|
||||||
}));
|
|
||||||
|
|
||||||
describe("kubeconfig-sync.source tests", () => {
|
describe("kubeconfig-sync.source tests", () => {
|
||||||
let computeDiff: ReturnType<typeof computeDiffFor>;
|
let computeKubeconfigDiff: ComputeKubeconfigDiff;
|
||||||
|
let configToModels: ConfigToModels;
|
||||||
|
let kubeconfigSyncs: ObservableMap<string, KubeconfigSyncValue>;
|
||||||
|
let clusters: Map<string, Cluster>;
|
||||||
|
let di: DiContainer;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
di = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||||
|
|
||||||
mockFs();
|
di.override(directoryForUserDataInjectable, () => "/some-directory-for-user-data");
|
||||||
|
di.override(directoryForTempInjectable, () => "/some-directory-for-temp");
|
||||||
|
|
||||||
di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
|
clusters = new Map();
|
||||||
di.override(directoryForTempInjectable, () => "some-directory-for-temp");
|
di.override(getClusterByIdInjectable, () => id => clusters.get(id));
|
||||||
di.override(kubectlBinaryNameInjectable, () => "kubectl");
|
|
||||||
di.override(kubectlDownloadingNormalizedArchInjectable, () => "amd64");
|
|
||||||
di.override(normalizedPlatformInjectable, () => "darwin");
|
|
||||||
|
|
||||||
di.permitSideEffects(fsInjectable);
|
kubeconfigSyncs = observable.map();
|
||||||
di.unoverride(clusterStoreInjectable);
|
|
||||||
di.permitSideEffects(clusterStoreInjectable);
|
|
||||||
di.permitSideEffects(getConfigurationFileModelInjectable);
|
|
||||||
|
|
||||||
computeDiff = computeDiffFor({
|
di.override(kubeconfigSyncsInjectable, () => kubeconfigSyncs);
|
||||||
directoryForKubeConfigs: di.inject(directoryForKubeConfigsInjectable),
|
|
||||||
createCluster: di.inject(createClusterInjectionToken),
|
|
||||||
clusterManager: di.inject(clusterManagerInjectable),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
computeKubeconfigDiff = di.inject(computeKubeconfigDiffInjectable);
|
||||||
mockFs.restore();
|
configToModels = di.inject(configToModelsInjectable);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("configsToModels", () => {
|
describe("configsToModels", () => {
|
||||||
@ -108,13 +93,13 @@ describe("kubeconfig-sync.source tests", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("computeDiff", () => {
|
describe("computeKubeconfigDiff", () => {
|
||||||
it("should leave an empty source empty if there are no entries", () => {
|
it("should leave an empty source empty if there are no entries", () => {
|
||||||
const contents = "";
|
const contents = "";
|
||||||
const rootSource = new ObservableMap<string, [Cluster, CatalogEntity]>();
|
const rootSource = new ObservableMap<string, [Cluster, CatalogEntity]>();
|
||||||
const filePath = "/bar";
|
const filePath = "/bar";
|
||||||
|
|
||||||
computeDiff(contents, rootSource, filePath);
|
computeKubeconfigDiff(contents, rootSource, filePath);
|
||||||
|
|
||||||
expect(rootSource.size).toBe(0);
|
expect(rootSource.size).toBe(0);
|
||||||
});
|
});
|
||||||
@ -149,9 +134,7 @@ describe("kubeconfig-sync.source tests", () => {
|
|||||||
const rootSource = new ObservableMap<string, [Cluster, CatalogEntity]>();
|
const rootSource = new ObservableMap<string, [Cluster, CatalogEntity]>();
|
||||||
const filePath = "/bar";
|
const filePath = "/bar";
|
||||||
|
|
||||||
fs.writeFileSync(filePath, contents);
|
computeKubeconfigDiff(contents, rootSource, filePath);
|
||||||
|
|
||||||
computeDiff(contents, rootSource, filePath);
|
|
||||||
|
|
||||||
expect(rootSource.size).toBe(1);
|
expect(rootSource.size).toBe(1);
|
||||||
|
|
||||||
@ -193,9 +176,7 @@ describe("kubeconfig-sync.source tests", () => {
|
|||||||
const rootSource = new ObservableMap<string, [Cluster, CatalogEntity]>();
|
const rootSource = new ObservableMap<string, [Cluster, CatalogEntity]>();
|
||||||
const filePath = "/bar";
|
const filePath = "/bar";
|
||||||
|
|
||||||
fs.writeFileSync(filePath, contents);
|
computeKubeconfigDiff(contents, rootSource, filePath);
|
||||||
|
|
||||||
computeDiff(contents, rootSource, filePath);
|
|
||||||
|
|
||||||
expect(rootSource.size).toBe(1);
|
expect(rootSource.size).toBe(1);
|
||||||
|
|
||||||
@ -204,7 +185,7 @@ describe("kubeconfig-sync.source tests", () => {
|
|||||||
expect(c.kubeConfigPath).toBe("/bar");
|
expect(c.kubeConfigPath).toBe("/bar");
|
||||||
expect(c.contextName).toBe("context-name");
|
expect(c.contextName).toBe("context-name");
|
||||||
|
|
||||||
computeDiff("{}", rootSource, filePath);
|
computeKubeconfigDiff("{}", rootSource, filePath);
|
||||||
|
|
||||||
expect(rootSource.size).toBe(0);
|
expect(rootSource.size).toBe(0);
|
||||||
});
|
});
|
||||||
@ -247,9 +228,7 @@ describe("kubeconfig-sync.source tests", () => {
|
|||||||
const rootSource = new ObservableMap<string, [Cluster, CatalogEntity]>();
|
const rootSource = new ObservableMap<string, [Cluster, CatalogEntity]>();
|
||||||
const filePath = "/bar";
|
const filePath = "/bar";
|
||||||
|
|
||||||
fs.writeFileSync(filePath, contents);
|
computeKubeconfigDiff(contents, rootSource, filePath);
|
||||||
|
|
||||||
computeDiff(contents, rootSource, filePath);
|
|
||||||
|
|
||||||
expect(rootSource.size).toBe(2);
|
expect(rootSource.size).toBe(2);
|
||||||
|
|
||||||
@ -289,7 +268,7 @@ describe("kubeconfig-sync.source tests", () => {
|
|||||||
currentContext: "foobar",
|
currentContext: "foobar",
|
||||||
});
|
});
|
||||||
|
|
||||||
computeDiff(newContents, rootSource, filePath);
|
computeKubeconfigDiff(newContents, rootSource, filePath);
|
||||||
|
|
||||||
expect(rootSource.size).toBe(1);
|
expect(rootSource.size).toBe(1);
|
||||||
|
|
||||||
@ -301,4 +280,181 @@ describe("kubeconfig-sync.source tests", () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("given a config file at /foobar/config", () => {
|
||||||
|
let manager: KubeconfigSyncManager;
|
||||||
|
let watchInstances: Map<string, Watcher<true>>;
|
||||||
|
let firstReadFoobarConfigSteam: ReadStream;
|
||||||
|
let secondReadFoobarConfigSteam: ReadStream;
|
||||||
|
let statMock: AsyncFnMock<Stat>;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
statMock = asyncFn();
|
||||||
|
di.override(statInjectable, () => statMock);
|
||||||
|
|
||||||
|
watchInstances = new Map();
|
||||||
|
di.override(watchInjectable, () => (path) => {
|
||||||
|
const fakeWatchInstance = getFakeWatchInstance();
|
||||||
|
|
||||||
|
watchInstances.set(path, fakeWatchInstance);
|
||||||
|
|
||||||
|
return fakeWatchInstance;
|
||||||
|
});
|
||||||
|
|
||||||
|
di.override(createReadFileStreamInjectable, () => (filePath) => {
|
||||||
|
if (filePath !== "/foobar/config") {
|
||||||
|
throw new Error(`unexpected file path "${filePath}"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!firstReadFoobarConfigSteam) {
|
||||||
|
return firstReadFoobarConfigSteam = getFakeReadStream(filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!secondReadFoobarConfigSteam) {
|
||||||
|
return secondReadFoobarConfigSteam = getFakeReadStream(filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
return getFakeReadStream(filePath);
|
||||||
|
});
|
||||||
|
|
||||||
|
manager = di.inject(kubeconfigSyncManagerInjectable);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
(firstReadFoobarConfigSteam as any) = undefined;
|
||||||
|
(secondReadFoobarConfigSteam as any) = undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not find any entities", () => {
|
||||||
|
expect(manager.source.get()).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when sync has started", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
manager.startSync();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not find any entities", () => {
|
||||||
|
expect(manager.source.get()).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when a file sync target for /foobar/config is added", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
kubeconfigSyncs.set("/foobar/config", {});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when stat resolves as not a directory", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await statMock.resolveSpecific(["/foobar/config"], {
|
||||||
|
isDirectory: () => false,
|
||||||
|
} as Stats);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when the watch emits that the file is added", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
strictGet(watchInstances, "/foobar/config").emit("add", "/foobar/config", {
|
||||||
|
size: foobarConfig.length,
|
||||||
|
} as Stats);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("starts to read the file", () => {
|
||||||
|
expect(firstReadFoobarConfigSteam).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when the data is read in", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
firstReadFoobarConfigSteam.emit("data", Buffer.from(foobarConfig));
|
||||||
|
firstReadFoobarConfigSteam.emit("end");
|
||||||
|
firstReadFoobarConfigSteam.emit("close");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should find a single entity", () => {
|
||||||
|
expect(manager.source.get().length).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when a folder sync target for /foobar is added", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
kubeconfigSyncs.set("/foobar", {});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when stat resolves as not a directory", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await statMock.resolveSpecific(["/foobar"], {
|
||||||
|
isDirectory: () => true,
|
||||||
|
} as Stats);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when the watch emits that the file is added", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
strictGet(watchInstances, "/foobar").emit("add", "/foobar/config", {
|
||||||
|
size: foobarConfig.length,
|
||||||
|
} as Stats);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("starts to read the file", () => {
|
||||||
|
expect(secondReadFoobarConfigSteam).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when the data is read in", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
secondReadFoobarConfigSteam.emit("data", Buffer.from(foobarConfig));
|
||||||
|
secondReadFoobarConfigSteam.emit("end");
|
||||||
|
secondReadFoobarConfigSteam.emit("close");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should still only find a single entity", () => {
|
||||||
|
expect(manager.source.get().length).toBe(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const getFakeWatchInstance = (): Watcher<true> => {
|
||||||
|
return Object.assign(new EventEmitter(), {
|
||||||
|
close: jest.fn().mockImplementation(async () => {}),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const getFakeReadStream = (path: string): ReadStream => {
|
||||||
|
return Object.assign(new EventEmitter(), {
|
||||||
|
path,
|
||||||
|
close: () => {},
|
||||||
|
push: () => true,
|
||||||
|
read: () => {},
|
||||||
|
}) as unknown as ReadStream;
|
||||||
|
};
|
||||||
|
|
||||||
|
const foobarConfig = JSON.stringify({
|
||||||
|
clusters: [{
|
||||||
|
name: "cluster-name",
|
||||||
|
cluster: {
|
||||||
|
server: "1.2.3.4",
|
||||||
|
},
|
||||||
|
skipTLSVerify: false,
|
||||||
|
}],
|
||||||
|
users: [{
|
||||||
|
name: "user-name",
|
||||||
|
}],
|
||||||
|
contexts: [{
|
||||||
|
name: "context-name",
|
||||||
|
context: {
|
||||||
|
cluster: "cluster-name",
|
||||||
|
user: "user-name",
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
name: "context-the-second",
|
||||||
|
context: {
|
||||||
|
cluster: "missing-cluster",
|
||||||
|
user: "user-name",
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
currentContext: "foobar",
|
||||||
});
|
});
|
||||||
|
|||||||
@ -0,0 +1,102 @@
|
|||||||
|
/**
|
||||||
|
* 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 { createHash } from "crypto";
|
||||||
|
import type { ObservableMap } from "mobx";
|
||||||
|
import { action } from "mobx";
|
||||||
|
import { homedir } from "os";
|
||||||
|
import directoryForKubeConfigsInjectable from "../../../common/app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable";
|
||||||
|
import type { CatalogEntity } from "../../../common/catalog";
|
||||||
|
import getClusterByIdInjectable from "../../../common/cluster-store/get-by-id.injectable";
|
||||||
|
import type { Cluster } from "../../../common/cluster/cluster";
|
||||||
|
import { loadConfigFromString } from "../../../common/kube-helpers";
|
||||||
|
import clustersThatAreBeingDeletedInjectable from "../../cluster/are-being-deleted.injectable";
|
||||||
|
import { catalogEntityFromCluster } from "../../cluster/manager";
|
||||||
|
import createClusterInjectable from "../../create-cluster/create-cluster.injectable";
|
||||||
|
import configToModelsInjectable from "./config-to-models.injectable";
|
||||||
|
import kubeconfigSyncLoggerInjectable from "./logger.injectable";
|
||||||
|
|
||||||
|
export type ComputeKubeconfigDiff = (contents: string, source: ObservableMap<string, [Cluster, CatalogEntity]>, filePath: string) => void;
|
||||||
|
|
||||||
|
const computeKubeconfigDiffInjectable = getInjectable({
|
||||||
|
id: "compute-kubeconfig-diff",
|
||||||
|
instantiate: (di): ComputeKubeconfigDiff => {
|
||||||
|
const directoryForKubeConfigs = di.inject(directoryForKubeConfigsInjectable);
|
||||||
|
const createCluster = di.inject(createClusterInjectable);
|
||||||
|
const clustersThatAreBeingDeleted = di.inject(clustersThatAreBeingDeletedInjectable);
|
||||||
|
const configToModels = di.inject(configToModelsInjectable);
|
||||||
|
const logger = di.inject(kubeconfigSyncLoggerInjectable);
|
||||||
|
const getClusterById = di.inject(getClusterByIdInjectable);
|
||||||
|
|
||||||
|
return action((contents, source, filePath) => {
|
||||||
|
try {
|
||||||
|
const { config, error } = loadConfigFromString(contents);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
logger.warn(`encountered errors while loading config: ${error.message}`, { filePath, details: error.details });
|
||||||
|
}
|
||||||
|
|
||||||
|
const rawModels = configToModels(config, filePath);
|
||||||
|
const models = new Map(rawModels.map(([model, configData]) => [model.contextName, [model, configData] as const]));
|
||||||
|
|
||||||
|
logger.debug(`File now has ${models.size} entries`, { filePath });
|
||||||
|
|
||||||
|
for (const [contextName, value] of source) {
|
||||||
|
const data = models.get(contextName);
|
||||||
|
|
||||||
|
// remove and disconnect clusters that were removed from the config
|
||||||
|
if (!data) {
|
||||||
|
// remove from the deleting set, so that if a new context of the same name is added, it isn't marked as deleting
|
||||||
|
clustersThatAreBeingDeleted.delete(value[0].id);
|
||||||
|
|
||||||
|
value[0].disconnect();
|
||||||
|
source.delete(contextName);
|
||||||
|
logger.debug(`Removed old cluster from sync`, { filePath, contextName });
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: For the update check we need to make sure that the config itself hasn't changed.
|
||||||
|
// Probably should make it so that cluster keeps a copy of the config in its memory and
|
||||||
|
// diff against that
|
||||||
|
|
||||||
|
// or update the model and mark it as not needed to be added
|
||||||
|
value[0].updateModel(data[0]);
|
||||||
|
models.delete(contextName);
|
||||||
|
logger.debug(`Updated old cluster from sync`, { filePath, contextName });
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [contextName, [model, configData]] of models) {
|
||||||
|
// add new clusters to the source
|
||||||
|
try {
|
||||||
|
const clusterId = createHash("md5").update(`${filePath}:${contextName}`).digest("hex");
|
||||||
|
const cluster = getClusterById(clusterId) ?? createCluster({ ...model, id: clusterId }, configData);
|
||||||
|
|
||||||
|
if (!cluster.apiUrl) {
|
||||||
|
throw new Error("Cluster constructor failed, see above error");
|
||||||
|
}
|
||||||
|
|
||||||
|
const entity = catalogEntityFromCluster(cluster);
|
||||||
|
|
||||||
|
if (!filePath.startsWith(directoryForKubeConfigs)) {
|
||||||
|
entity.metadata.labels.file = filePath.replace(homedir(), "~");
|
||||||
|
}
|
||||||
|
source.set(contextName, [cluster, entity]);
|
||||||
|
|
||||||
|
logger.debug(`Added new cluster from sync`, { filePath, contextName });
|
||||||
|
} catch (error) {
|
||||||
|
logger.warn(`Failed to create cluster from model: ${error}`, { filePath, contextName });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.warn(`Failed to compute diff: ${error}`, { filePath });
|
||||||
|
source.clear(); // clear source if we have failed so as to not show outdated information
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug("Finished computing diff", { filePath });
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default computeKubeconfigDiffInjectable;
|
||||||
@ -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 { KubeConfig } from "@kubernetes/client-node";
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import type { ClusterConfigData, UpdateClusterModel } from "../../../common/cluster-types";
|
||||||
|
import { splitConfig } from "../../../common/kube-helpers";
|
||||||
|
import kubeconfigSyncLoggerInjectable from "./logger.injectable";
|
||||||
|
|
||||||
|
export type ConfigToModels = (rootConfig: KubeConfig, filePath: string) => [UpdateClusterModel, ClusterConfigData][];
|
||||||
|
|
||||||
|
const configToModelsInjectable = getInjectable({
|
||||||
|
id: "config-to-models",
|
||||||
|
instantiate: (di): ConfigToModels => {
|
||||||
|
const logger = di.inject(kubeconfigSyncLoggerInjectable);
|
||||||
|
|
||||||
|
return (rootConfig, filePath) => {
|
||||||
|
const validConfigs: ReturnType<ConfigToModels> = [];
|
||||||
|
|
||||||
|
for (const { config, validationResult } of splitConfig(rootConfig)) {
|
||||||
|
if (validationResult.error) {
|
||||||
|
logger.debug(`context failed validation: ${validationResult.error}`, { context: config.currentContext, filePath });
|
||||||
|
} else {
|
||||||
|
validConfigs.push([
|
||||||
|
{
|
||||||
|
kubeConfigPath: filePath,
|
||||||
|
contextName: config.currentContext,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
clusterServerUrl: validationResult.cluster.server,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return validConfigs;
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default configToModelsInjectable;
|
||||||
@ -0,0 +1,90 @@
|
|||||||
|
/**
|
||||||
|
* 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 { Stats } from "fs";
|
||||||
|
import { constants } from "fs";
|
||||||
|
import type { ObservableMap } from "mobx";
|
||||||
|
import type { Readable } from "stream";
|
||||||
|
import type { CatalogEntity } from "../../../common/catalog";
|
||||||
|
import type { Cluster } from "../../../common/cluster/cluster";
|
||||||
|
import createReadFileStreamInjectable from "../../../common/fs/create-read-file-stream.injectable";
|
||||||
|
import type { Disposer } from "../../../common/utils";
|
||||||
|
import { bytesToUnits, noop } from "../../../common/utils";
|
||||||
|
import computeKubeconfigDiffInjectable from "./compute-diff.injectable";
|
||||||
|
import kubeconfigSyncLoggerInjectable from "./logger.injectable";
|
||||||
|
|
||||||
|
export interface DiffChangedKubeconfigArgs {
|
||||||
|
filePath: string;
|
||||||
|
source: ObservableMap<string, [Cluster, CatalogEntity]>;
|
||||||
|
stats: Stats;
|
||||||
|
maxAllowedFileReadSize: number;
|
||||||
|
}
|
||||||
|
export type DiffChangedKubeconfig = (args: DiffChangedKubeconfigArgs) => Disposer;
|
||||||
|
|
||||||
|
const diffChangedKubeconfigInjectable = getInjectable({
|
||||||
|
id: "diff-changed-kubeconfig",
|
||||||
|
instantiate: (di): DiffChangedKubeconfig => {
|
||||||
|
const computeKubeconfigDiff = di.inject(computeKubeconfigDiffInjectable);
|
||||||
|
const logger = di.inject(kubeconfigSyncLoggerInjectable);
|
||||||
|
const createReadFileStream = di.inject(createReadFileStreamInjectable);
|
||||||
|
|
||||||
|
return ({ filePath, maxAllowedFileReadSize, source, stats }) => {
|
||||||
|
logger.debug(`file changed`, { filePath });
|
||||||
|
|
||||||
|
if (stats.size >= maxAllowedFileReadSize) {
|
||||||
|
logger.warn(`skipping ${filePath}: size=${bytesToUnits(stats.size)} is larger than maxSize=${bytesToUnits(maxAllowedFileReadSize)}`);
|
||||||
|
source.clear();
|
||||||
|
|
||||||
|
return noop;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fileReader = createReadFileStream(filePath, {
|
||||||
|
mode: constants.O_RDONLY,
|
||||||
|
});
|
||||||
|
const readStream = fileReader as Readable;
|
||||||
|
const decoder = new TextDecoder("utf-8", { fatal: true });
|
||||||
|
let fileString = "";
|
||||||
|
let closed = false;
|
||||||
|
|
||||||
|
const cleanup = () => {
|
||||||
|
closed = true;
|
||||||
|
fileReader.close(); // This may not close the stream.
|
||||||
|
// Artificially marking end-of-stream, as if the underlying resource had
|
||||||
|
// indicated end-of-file by itself, allows the stream to close.
|
||||||
|
// This does not cancel pending read operations, and if there is such an
|
||||||
|
// operation, the process may still not be able to exit successfully
|
||||||
|
// until it finishes.
|
||||||
|
fileReader.push(null);
|
||||||
|
fileReader.read(0);
|
||||||
|
readStream.removeAllListeners();
|
||||||
|
};
|
||||||
|
|
||||||
|
readStream
|
||||||
|
.on("data", (chunk: Buffer) => {
|
||||||
|
try {
|
||||||
|
fileString += decoder.decode(chunk, { stream: true });
|
||||||
|
} catch (error) {
|
||||||
|
logger.warn(`skipping ${filePath}: ${error}`);
|
||||||
|
source.clear();
|
||||||
|
cleanup();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.on("close", () => cleanup())
|
||||||
|
.on("error", error => {
|
||||||
|
cleanup();
|
||||||
|
logger.warn(`failed to read file: ${error}`, { filePath });
|
||||||
|
})
|
||||||
|
.on("end", () => {
|
||||||
|
if (!closed) {
|
||||||
|
computeKubeconfigDiff(fileString, source, filePath);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return cleanup;
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default diffChangedKubeconfigInjectable;
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
/**
|
||||||
|
* 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 prefixedLoggerInjectable from "../../../common/logger/prefixed-logger.injectable";
|
||||||
|
|
||||||
|
const kubeconfigSyncLoggerInjectable = getInjectable({
|
||||||
|
id: "kubeconfig-sync-logger",
|
||||||
|
instantiate: (di) => di.inject(prefixedLoggerInjectable, "KUBECONFIG-SYNC"),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default kubeconfigSyncLoggerInjectable;
|
||||||
@ -5,18 +5,18 @@
|
|||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import directoryForKubeConfigsInjectable from "../../../common/app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable";
|
import directoryForKubeConfigsInjectable from "../../../common/app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable";
|
||||||
import { KubeconfigSyncManager } from "./manager";
|
import { KubeconfigSyncManager } from "./manager";
|
||||||
import { createClusterInjectionToken } from "../../../common/cluster/create-cluster-injection-token";
|
import kubeconfigSyncLoggerInjectable from "./logger.injectable";
|
||||||
import clusterManagerInjectable from "../../cluster-manager.injectable";
|
import watchKubeconfigFileChangesInjectable from "./watch-file-changes.injectable";
|
||||||
import catalogEntityRegistryInjectable from "../../catalog/entity-registry.injectable";
|
import kubeconfigSyncsInjectable from "../../../common/user-store/kubeconfig-syncs.injectable";
|
||||||
|
|
||||||
const kubeconfigSyncManagerInjectable = getInjectable({
|
const kubeconfigSyncManagerInjectable = getInjectable({
|
||||||
id: "kubeconfig-sync-manager",
|
id: "kubeconfig-sync-manager",
|
||||||
|
|
||||||
instantiate: (di) => new KubeconfigSyncManager({
|
instantiate: (di) => new KubeconfigSyncManager({
|
||||||
directoryForKubeConfigs: di.inject(directoryForKubeConfigsInjectable),
|
directoryForKubeConfigs: di.inject(directoryForKubeConfigsInjectable),
|
||||||
createCluster: di.inject(createClusterInjectionToken),
|
logger: di.inject(kubeconfigSyncLoggerInjectable),
|
||||||
clusterManager: di.inject(clusterManagerInjectable),
|
watchKubeconfigFileChanges: di.inject(watchKubeconfigFileChangesInjectable),
|
||||||
entityRegistry: di.inject(catalogEntityRegistryInjectable),
|
kubeconfigSyncs: di.inject(kubeconfigSyncsInjectable),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -4,97 +4,61 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import type { IComputedValue, ObservableMap } from "mobx";
|
import type { IComputedValue, ObservableMap } from "mobx";
|
||||||
import { action, observable, computed, runInAction, makeObservable, observe } from "mobx";
|
import { action, observable, computed, makeObservable, observe } from "mobx";
|
||||||
import type { CatalogEntity } from "../../../common/catalog";
|
import type { CatalogEntity } from "../../../common/catalog";
|
||||||
import type { FSWatcher } from "chokidar";
|
|
||||||
import { watch } from "chokidar";
|
|
||||||
import type { Stats } from "fs";
|
|
||||||
import fs from "fs";
|
|
||||||
import path from "path";
|
|
||||||
import type { Disposer } from "../../../common/utils";
|
import type { Disposer } from "../../../common/utils";
|
||||||
import { disposer, bytesToUnits, getOrInsertWith, iter, noop } from "../../../common/utils";
|
import { iter } from "../../../common/utils";
|
||||||
import logger from "../../logger";
|
import type { KubeconfigSyncValue } from "../../../common/user-store";
|
||||||
import type { KubeConfig } from "@kubernetes/client-node";
|
import type { Logger } from "../../../common/logger";
|
||||||
import { loadConfigFromString, splitConfig } from "../../../common/kube-helpers";
|
import type { WatchKubeconfigFileChanges } from "./watch-file-changes.injectable";
|
||||||
import type { ClusterManager } from "../../cluster-manager";
|
|
||||||
import { catalogEntityFromCluster } from "../../cluster-manager";
|
|
||||||
import { UserStore } from "../../../common/user-store";
|
|
||||||
import { ClusterStore } from "../../../common/cluster-store/cluster-store";
|
|
||||||
import { createHash } from "crypto";
|
|
||||||
import { homedir } from "os";
|
|
||||||
import globToRegExp from "glob-to-regexp";
|
|
||||||
import { inspect } from "util";
|
|
||||||
import type { ClusterConfigData, UpdateClusterModel } from "../../../common/cluster-types";
|
|
||||||
import type { Cluster } from "../../../common/cluster/cluster";
|
|
||||||
import type { CatalogEntityRegistry } from "../../catalog/entity-registry";
|
|
||||||
import type { CreateCluster } from "../../../common/cluster/create-cluster-injection-token";
|
|
||||||
|
|
||||||
const logPrefix = "[KUBECONFIG-SYNC]:";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is the list of globs of which files are ignored when under a folder sync
|
|
||||||
*/
|
|
||||||
const ignoreGlobs = [
|
|
||||||
"*.lock", // kubectl lock files
|
|
||||||
"*.swp", // vim swap files
|
|
||||||
".DS_Store", // macOS specific
|
|
||||||
].map(rawGlob => ({
|
|
||||||
rawGlob,
|
|
||||||
matcher: globToRegExp(rawGlob),
|
|
||||||
}));
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This should be much larger than any kubeconfig text file
|
|
||||||
*
|
|
||||||
* Even if you have a cert-file, key-file, and client-cert files that is only
|
|
||||||
* 12kb of extra data (at 4096 bytes each) which allows for around 150 entries.
|
|
||||||
*/
|
|
||||||
const folderSyncMaxAllowedFileReadSize = 2 * 1024 * 1024; // 2 MiB
|
|
||||||
const fileSyncMaxAllowedFileReadSize = 16 * folderSyncMaxAllowedFileReadSize; // 32 MiB
|
|
||||||
|
|
||||||
interface KubeconfigSyncManagerDependencies {
|
interface KubeconfigSyncManagerDependencies {
|
||||||
readonly directoryForKubeConfigs: string;
|
readonly directoryForKubeConfigs: string;
|
||||||
readonly entityRegistry: CatalogEntityRegistry;
|
readonly logger: Logger;
|
||||||
readonly clusterManager: ClusterManager;
|
readonly kubeconfigSyncs: ObservableMap<string, KubeconfigSyncValue>;
|
||||||
createCluster: CreateCluster;
|
watchKubeconfigFileChanges: WatchKubeconfigFileChanges;
|
||||||
}
|
}
|
||||||
|
|
||||||
const kubeConfigSyncName = "lens:kube-sync";
|
|
||||||
|
|
||||||
export class KubeconfigSyncManager {
|
export class KubeconfigSyncManager {
|
||||||
protected readonly sources = observable.map<string, [IComputedValue<CatalogEntity[]>, Disposer]>();
|
protected readonly sources = observable.map<string, [IComputedValue<CatalogEntity[]>, Disposer]>();
|
||||||
protected syncing = false;
|
|
||||||
protected syncListDisposer?: Disposer;
|
protected syncListDisposer?: Disposer;
|
||||||
|
|
||||||
constructor(protected readonly dependencies: KubeconfigSyncManagerDependencies) {
|
constructor(protected readonly dependencies: KubeconfigSyncManagerDependencies) {
|
||||||
makeObservable(this);
|
makeObservable(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public readonly source = computed(() => {
|
||||||
|
/**
|
||||||
|
* This prevents multiple overlapping syncs from leading to multiple entities with the same IDs
|
||||||
|
*/
|
||||||
|
const seenIds = new Set<string>();
|
||||||
|
|
||||||
|
return (
|
||||||
|
iter.pipeline(this.sources.values())
|
||||||
|
.flatMap(([entities]) => entities.get())
|
||||||
|
.filter(entity => {
|
||||||
|
const alreadySeen = seenIds.has(entity.getId());
|
||||||
|
|
||||||
|
seenIds.add(entity.getId());
|
||||||
|
|
||||||
|
return !alreadySeen;
|
||||||
|
})
|
||||||
|
.collect(items => [...items])
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
@action
|
@action
|
||||||
startSync(): void {
|
startSync(): void {
|
||||||
if (this.syncing) {
|
this.dependencies.logger.info(`starting requested syncs`);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.syncing = true;
|
|
||||||
|
|
||||||
logger.info(`${logPrefix} starting requested syncs`);
|
|
||||||
|
|
||||||
this.dependencies.entityRegistry.addComputedSource(kubeConfigSyncName, computed(() => (
|
|
||||||
Array.from(iter.flatMap(
|
|
||||||
this.sources.values(),
|
|
||||||
([entities]) => entities.get(),
|
|
||||||
))
|
|
||||||
)));
|
|
||||||
|
|
||||||
// This must be done so that c&p-ed clusters are visible
|
// This must be done so that c&p-ed clusters are visible
|
||||||
this.startNewSync(this.dependencies.directoryForKubeConfigs);
|
this.startNewSync(this.dependencies.directoryForKubeConfigs);
|
||||||
|
|
||||||
for (const filePath of UserStore.getInstance().syncKubeconfigEntries.keys()) {
|
for (const filePath of this.dependencies.kubeconfigSyncs.keys()) {
|
||||||
this.startNewSync(filePath);
|
this.startNewSync(filePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.syncListDisposer = observe(UserStore.getInstance().syncKubeconfigEntries, change => {
|
this.syncListDisposer = observe(this.dependencies.kubeconfigSyncs, change => {
|
||||||
switch (change.type) {
|
switch (change.type) {
|
||||||
case "add":
|
case "add":
|
||||||
this.startNewSync(change.name);
|
this.startNewSync(change.name);
|
||||||
@ -108,275 +72,38 @@ export class KubeconfigSyncManager {
|
|||||||
|
|
||||||
@action
|
@action
|
||||||
stopSync() {
|
stopSync() {
|
||||||
|
this.dependencies.logger.info(`stopping requested syncs`);
|
||||||
this.syncListDisposer?.();
|
this.syncListDisposer?.();
|
||||||
|
|
||||||
for (const filePath of this.sources.keys()) {
|
for (const filePath of this.sources.keys()) {
|
||||||
this.stopOldSync(filePath);
|
this.stopOldSync(filePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.dependencies.entityRegistry.removeSource(kubeConfigSyncName);
|
|
||||||
this.syncing = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
protected startNewSync(filePath: string): void {
|
protected startNewSync(filePath: string): void {
|
||||||
if (this.sources.has(filePath)) {
|
if (this.sources.has(filePath)) {
|
||||||
// don't start a new sync if we already have one
|
// don't start a new sync if we already have one
|
||||||
return void logger.debug(`${logPrefix} already syncing file/folder`, { filePath });
|
return this.dependencies.logger.debug(`already syncing file/folder`, { filePath });
|
||||||
}
|
}
|
||||||
|
|
||||||
this.sources.set(
|
this.sources.set(
|
||||||
filePath,
|
filePath,
|
||||||
watchFileChanges(filePath, this.dependencies),
|
this.dependencies.watchKubeconfigFileChanges(filePath),
|
||||||
);
|
);
|
||||||
|
|
||||||
logger.info(`${logPrefix} starting sync of file/folder`, { filePath });
|
this.dependencies.logger.info(`starting sync of file/folder`, { filePath });
|
||||||
logger.debug(`${logPrefix} ${this.sources.size} files/folders watched`, { files: Array.from(this.sources.keys()) });
|
this.dependencies.logger.debug(`${this.sources.size} files/folders watched`, { files: Array.from(this.sources.keys()) });
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
protected stopOldSync(filePath: string): void {
|
protected stopOldSync(filePath: string): void {
|
||||||
if (!this.sources.delete(filePath)) {
|
if (!this.sources.delete(filePath)) {
|
||||||
// already stopped
|
// already stopped
|
||||||
return void logger.debug(`${logPrefix} no syncing file/folder to stop`, { filePath });
|
return this.dependencies.logger.debug(`no syncing file/folder to stop`, { filePath });
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info(`${logPrefix} stopping sync of file/folder`, { filePath });
|
this.dependencies.logger.info(`stopping sync of file/folder`, { filePath });
|
||||||
logger.debug(`${logPrefix} ${this.sources.size} files/folders watched`, { files: Array.from(this.sources.keys()) });
|
this.dependencies.logger.debug(`${this.sources.size} files/folders watched`, { files: Array.from(this.sources.keys()) });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// exported for testing
|
|
||||||
export function configToModels(rootConfig: KubeConfig, filePath: string): [UpdateClusterModel, ClusterConfigData][] {
|
|
||||||
const validConfigs: ReturnType<typeof configToModels> = [];
|
|
||||||
|
|
||||||
for (const { config, validationResult } of splitConfig(rootConfig)) {
|
|
||||||
if (validationResult.error) {
|
|
||||||
logger.debug(`${logPrefix} context failed validation: ${validationResult.error}`, { context: config.currentContext, filePath });
|
|
||||||
} else {
|
|
||||||
validConfigs.push([
|
|
||||||
{
|
|
||||||
kubeConfigPath: filePath,
|
|
||||||
contextName: config.currentContext,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
clusterServerUrl: validationResult.cluster.server,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return validConfigs;
|
|
||||||
}
|
|
||||||
|
|
||||||
type RootSourceValue = [Cluster, CatalogEntity];
|
|
||||||
type RootSource = ObservableMap<string, RootSourceValue>;
|
|
||||||
|
|
||||||
interface ComputeDiffDependencies {
|
|
||||||
directoryForKubeConfigs: string;
|
|
||||||
createCluster: CreateCluster;
|
|
||||||
clusterManager: ClusterManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
// exported for testing
|
|
||||||
export const computeDiff = ({ directoryForKubeConfigs, createCluster, clusterManager }: ComputeDiffDependencies) => (contents: string, source: RootSource, filePath: string): void => {
|
|
||||||
runInAction(() => {
|
|
||||||
try {
|
|
||||||
const { config, error } = loadConfigFromString(contents);
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
logger.warn(`${logPrefix} encountered errors while loading config: ${error.message}`, { filePath, details: error.details });
|
|
||||||
}
|
|
||||||
|
|
||||||
const rawModels = configToModels(config, filePath);
|
|
||||||
const models = new Map(rawModels.map(([model, configData]) => [model.contextName, [model, configData] as const]));
|
|
||||||
|
|
||||||
logger.debug(`${logPrefix} File now has ${models.size} entries`, { filePath });
|
|
||||||
|
|
||||||
for (const [contextName, value] of source) {
|
|
||||||
const data = models.get(contextName);
|
|
||||||
|
|
||||||
// remove and disconnect clusters that were removed from the config
|
|
||||||
if (!data) {
|
|
||||||
// remove from the deleting set, so that if a new context of the same name is added, it isn't marked as deleting
|
|
||||||
clusterManager.deleting.delete(value[0].id);
|
|
||||||
|
|
||||||
value[0].disconnect();
|
|
||||||
source.delete(contextName);
|
|
||||||
logger.debug(`${logPrefix} Removed old cluster from sync`, { filePath, contextName });
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: For the update check we need to make sure that the config itself hasn't changed.
|
|
||||||
// Probably should make it so that cluster keeps a copy of the config in its memory and
|
|
||||||
// diff against that
|
|
||||||
|
|
||||||
// or update the model and mark it as not needed to be added
|
|
||||||
value[0].updateModel(data[0]);
|
|
||||||
models.delete(contextName);
|
|
||||||
logger.debug(`${logPrefix} Updated old cluster from sync`, { filePath, contextName });
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const [contextName, [model, configData]] of models) {
|
|
||||||
// add new clusters to the source
|
|
||||||
try {
|
|
||||||
const clusterId = createHash("md5").update(`${filePath}:${contextName}`).digest("hex");
|
|
||||||
|
|
||||||
const cluster = ClusterStore.getInstance().getById(clusterId) || createCluster({ ...model, id: clusterId }, configData);
|
|
||||||
|
|
||||||
if (!cluster.apiUrl) {
|
|
||||||
throw new Error("Cluster constructor failed, see above error");
|
|
||||||
}
|
|
||||||
|
|
||||||
const entity = catalogEntityFromCluster(cluster);
|
|
||||||
|
|
||||||
if (!filePath.startsWith(directoryForKubeConfigs)) {
|
|
||||||
entity.metadata.labels.file = filePath.replace(homedir(), "~");
|
|
||||||
}
|
|
||||||
source.set(contextName, [cluster, entity]);
|
|
||||||
|
|
||||||
logger.debug(`${logPrefix} Added new cluster from sync`, { filePath, contextName });
|
|
||||||
} catch (error) {
|
|
||||||
logger.warn(`${logPrefix} Failed to create cluster from model: ${error}`, { filePath, contextName });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
logger.warn(`${logPrefix} Failed to compute diff: ${error}`, { filePath });
|
|
||||||
source.clear(); // clear source if we have failed so as to not show outdated information
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
interface DiffChangedConfigArgs {
|
|
||||||
filePath: string;
|
|
||||||
source: RootSource;
|
|
||||||
stats: fs.Stats;
|
|
||||||
maxAllowedFileReadSize: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
const diffChangedConfigFor = (dependencies: ComputeDiffDependencies) => ({ filePath, source, stats, maxAllowedFileReadSize }: DiffChangedConfigArgs): Disposer => {
|
|
||||||
logger.debug(`${logPrefix} file changed`, { filePath });
|
|
||||||
|
|
||||||
if (stats.size >= maxAllowedFileReadSize) {
|
|
||||||
logger.warn(`${logPrefix} skipping ${filePath}: size=${bytesToUnits(stats.size)} is larger than maxSize=${bytesToUnits(maxAllowedFileReadSize)}`);
|
|
||||||
source.clear();
|
|
||||||
|
|
||||||
return noop;
|
|
||||||
}
|
|
||||||
|
|
||||||
const controller = new AbortController();
|
|
||||||
const fileContentsP = fs.promises.readFile(filePath, {
|
|
||||||
signal: controller.signal,
|
|
||||||
});
|
|
||||||
const cleanup = disposer(
|
|
||||||
() => controller.abort(),
|
|
||||||
);
|
|
||||||
|
|
||||||
fileContentsP
|
|
||||||
.then((fileData) => {
|
|
||||||
const decoder = new TextDecoder("utf-8", { fatal: true });
|
|
||||||
|
|
||||||
try {
|
|
||||||
const fileString = decoder.decode(fileData);
|
|
||||||
|
|
||||||
computeDiff(dependencies)(fileString, source, filePath);
|
|
||||||
} catch (error) {
|
|
||||||
logger.warn(`${logPrefix} skipping ${filePath}: ${error}`);
|
|
||||||
source.clear();
|
|
||||||
cleanup();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
if (controller.signal.aborted) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.warn(`${logPrefix} failed to read file: ${error}`, { filePath });
|
|
||||||
cleanup();
|
|
||||||
});
|
|
||||||
|
|
||||||
return cleanup;
|
|
||||||
};
|
|
||||||
|
|
||||||
const watchFileChanges = (filePath: string, dependencies: ComputeDiffDependencies): [IComputedValue<CatalogEntity[]>, Disposer] => {
|
|
||||||
const rootSource = observable.map<string, ObservableMap<string, RootSourceValue>>();
|
|
||||||
const derivedSource = computed(() => Array.from(iter.flatMap(rootSource.values(), from => iter.map(from.values(), child => child[1]))));
|
|
||||||
|
|
||||||
let watcher: FSWatcher;
|
|
||||||
|
|
||||||
(async () => {
|
|
||||||
try {
|
|
||||||
const stat = await fs.promises.stat(filePath);
|
|
||||||
const isFolderSync = stat.isDirectory();
|
|
||||||
const cleanupFns = new Map<string, Disposer>();
|
|
||||||
const maxAllowedFileReadSize = isFolderSync
|
|
||||||
? folderSyncMaxAllowedFileReadSize
|
|
||||||
: fileSyncMaxAllowedFileReadSize;
|
|
||||||
|
|
||||||
watcher = watch(filePath, {
|
|
||||||
followSymlinks: true,
|
|
||||||
depth: isFolderSync ? 0 : 1, // DIRs works with 0 but files need 1 (bug: https://github.com/paulmillr/chokidar/issues/1095)
|
|
||||||
disableGlobbing: true,
|
|
||||||
ignorePermissionErrors: true,
|
|
||||||
usePolling: false,
|
|
||||||
awaitWriteFinish: {
|
|
||||||
pollInterval: 100,
|
|
||||||
stabilityThreshold: 1000,
|
|
||||||
},
|
|
||||||
atomic: 150, // for "atomic writes"
|
|
||||||
alwaysStat: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const diffChangedConfig = diffChangedConfigFor(dependencies);
|
|
||||||
|
|
||||||
watcher
|
|
||||||
.on("change", (childFilePath, stats: Stats): void => {
|
|
||||||
const cleanup = cleanupFns.get(childFilePath);
|
|
||||||
|
|
||||||
if (!cleanup) {
|
|
||||||
// file was previously ignored, do nothing
|
|
||||||
return void logger.debug(`${logPrefix} ${inspect(childFilePath)} that should have been previously ignored has changed. Doing nothing`);
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup();
|
|
||||||
cleanupFns.set(childFilePath, diffChangedConfig({
|
|
||||||
filePath: childFilePath,
|
|
||||||
source: getOrInsertWith(rootSource, childFilePath, observable.map),
|
|
||||||
stats,
|
|
||||||
maxAllowedFileReadSize,
|
|
||||||
}));
|
|
||||||
})
|
|
||||||
.on("add", (childFilePath, stats: Stats): void => {
|
|
||||||
if (isFolderSync) {
|
|
||||||
const fileName = path.basename(childFilePath);
|
|
||||||
|
|
||||||
for (const ignoreGlob of ignoreGlobs) {
|
|
||||||
if (ignoreGlob.matcher.test(fileName)) {
|
|
||||||
return void logger.info(`${logPrefix} ignoring ${inspect(childFilePath)} due to ignore glob: ${ignoreGlob.rawGlob}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanupFns.set(childFilePath, diffChangedConfig({
|
|
||||||
filePath: childFilePath,
|
|
||||||
source: getOrInsertWith(rootSource, childFilePath, observable.map),
|
|
||||||
stats,
|
|
||||||
maxAllowedFileReadSize,
|
|
||||||
}));
|
|
||||||
})
|
|
||||||
.on("unlink", (childFilePath) => {
|
|
||||||
cleanupFns.get(childFilePath)?.();
|
|
||||||
cleanupFns.delete(childFilePath);
|
|
||||||
rootSource.delete(childFilePath);
|
|
||||||
})
|
|
||||||
.on("error", error => logger.error(`${logPrefix} watching file/folder failed: ${error}`, { filePath }));
|
|
||||||
} catch (error) {
|
|
||||||
console.log((error as { stack: unknown }).stack);
|
|
||||||
logger.warn(`${logPrefix} failed to start watching changes: ${error}`);
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|
||||||
return [derivedSource, () => {
|
|
||||||
watcher?.close();
|
|
||||||
}];
|
|
||||||
};
|
|
||||||
|
|||||||
@ -0,0 +1,134 @@
|
|||||||
|
/**
|
||||||
|
* 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 GlobToRegExp from "glob-to-regexp";
|
||||||
|
import type { IComputedValue, ObservableMap } from "mobx";
|
||||||
|
import { computed, observable } from "mobx";
|
||||||
|
import path from "path";
|
||||||
|
import { inspect } from "util";
|
||||||
|
import type { CatalogEntity } from "../../../common/catalog";
|
||||||
|
import type { Cluster } from "../../../common/cluster/cluster";
|
||||||
|
import statInjectable from "../../../common/fs/stat/stat.injectable";
|
||||||
|
import type { Watcher } from "../../../common/fs/watch/watch.injectable";
|
||||||
|
import watchInjectable from "../../../common/fs/watch/watch.injectable";
|
||||||
|
import type { Disposer } from "../../../common/utils";
|
||||||
|
import { getOrInsertWith, iter } from "../../../common/utils";
|
||||||
|
import diffChangedKubeconfigInjectable from "./diff-changed-kubeconfig.injectable";
|
||||||
|
import kubeconfigSyncLoggerInjectable from "./logger.injectable";
|
||||||
|
|
||||||
|
export type WatchKubeconfigFileChanges = (filepath: string) => [IComputedValue<CatalogEntity[]>, Disposer];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the list of globs of which files are ignored when under a folder sync
|
||||||
|
*/
|
||||||
|
const ignoreGlobs = [
|
||||||
|
"*.lock", // kubectl lock files
|
||||||
|
"*.swp", // vim swap files
|
||||||
|
".DS_Store", // macOS specific
|
||||||
|
].map(rawGlob => ({
|
||||||
|
rawGlob,
|
||||||
|
matcher: GlobToRegExp(rawGlob),
|
||||||
|
}));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This should be much larger than any kubeconfig text file
|
||||||
|
*
|
||||||
|
* Even if you have a cert-file, key-file, and client-cert files that is only
|
||||||
|
* 12kb of extra data (at 4096 bytes each) which allows for around 150 entries.
|
||||||
|
*/
|
||||||
|
const folderSyncMaxAllowedFileReadSize = 2 * 1024 * 1024; // 2 MiB
|
||||||
|
const fileSyncMaxAllowedFileReadSize = 16 * folderSyncMaxAllowedFileReadSize; // 32 MiB
|
||||||
|
|
||||||
|
const watchKubeconfigFileChangesInjectable = getInjectable({
|
||||||
|
id: "watch-kubeconfig-file-changes",
|
||||||
|
instantiate: (di): WatchKubeconfigFileChanges => {
|
||||||
|
const diffChangedKubeconfig = di.inject(diffChangedKubeconfigInjectable);
|
||||||
|
const logger = di.inject(kubeconfigSyncLoggerInjectable);
|
||||||
|
const stat = di.inject(statInjectable);
|
||||||
|
const watch = di.inject(watchInjectable);
|
||||||
|
|
||||||
|
return (filePath) => {
|
||||||
|
const rootSource = observable.map<string, ObservableMap<string, [Cluster, CatalogEntity]>>();
|
||||||
|
const derivedSource = computed(() => Array.from(iter.flatMap(rootSource.values(), from => iter.map(from.values(), child => child[1]))));
|
||||||
|
|
||||||
|
let watcher: Watcher<true>;
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
const stats = await stat(filePath);
|
||||||
|
const isFolderSync = stats.isDirectory();
|
||||||
|
const cleanupFns = new Map<string, Disposer>();
|
||||||
|
const maxAllowedFileReadSize = isFolderSync
|
||||||
|
? folderSyncMaxAllowedFileReadSize
|
||||||
|
: fileSyncMaxAllowedFileReadSize;
|
||||||
|
|
||||||
|
watcher = watch<true>(filePath, {
|
||||||
|
followSymlinks: true,
|
||||||
|
depth: isFolderSync ? 0 : 1, // DIRs works with 0 but files need 1 (bug: https://github.com/paulmillr/chokidar/issues/1095)
|
||||||
|
disableGlobbing: true,
|
||||||
|
ignorePermissionErrors: true,
|
||||||
|
usePolling: false,
|
||||||
|
awaitWriteFinish: {
|
||||||
|
pollInterval: 100,
|
||||||
|
stabilityThreshold: 1000,
|
||||||
|
},
|
||||||
|
atomic: 150, // for "atomic writes"
|
||||||
|
alwaysStat: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
watcher
|
||||||
|
.on("change", (childFilePath, stats): void => {
|
||||||
|
const cleanup = cleanupFns.get(childFilePath);
|
||||||
|
|
||||||
|
if (!cleanup) {
|
||||||
|
// file was previously ignored, do nothing
|
||||||
|
return void logger.debug(`${inspect(childFilePath)} that should have been previously ignored has changed. Doing nothing`);
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup();
|
||||||
|
cleanupFns.set(childFilePath, diffChangedKubeconfig({
|
||||||
|
filePath: childFilePath,
|
||||||
|
source: getOrInsertWith(rootSource, childFilePath, observable.map),
|
||||||
|
stats,
|
||||||
|
maxAllowedFileReadSize,
|
||||||
|
}));
|
||||||
|
})
|
||||||
|
.on("add", (childFilePath, stats): void => {
|
||||||
|
if (isFolderSync) {
|
||||||
|
const fileName = path.basename(childFilePath);
|
||||||
|
|
||||||
|
for (const ignoreGlob of ignoreGlobs) {
|
||||||
|
if (ignoreGlob.matcher.test(fileName)) {
|
||||||
|
return void logger.info(`ignoring ${inspect(childFilePath)} due to ignore glob: ${ignoreGlob.rawGlob}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanupFns.set(childFilePath, diffChangedKubeconfig({
|
||||||
|
filePath: childFilePath,
|
||||||
|
source: getOrInsertWith(rootSource, childFilePath, observable.map),
|
||||||
|
stats,
|
||||||
|
maxAllowedFileReadSize,
|
||||||
|
}));
|
||||||
|
})
|
||||||
|
.on("unlink", (childFilePath) => {
|
||||||
|
cleanupFns.get(childFilePath)?.();
|
||||||
|
cleanupFns.delete(childFilePath);
|
||||||
|
rootSource.delete(childFilePath);
|
||||||
|
})
|
||||||
|
.on("error", error => logger.error(`watching file/folder failed: ${error}`, { filePath }));
|
||||||
|
} catch (error) {
|
||||||
|
logger.warn(`failed to start watching changes: ${error}`);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
return [derivedSource, () => {
|
||||||
|
watcher?.close();
|
||||||
|
}];
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default watchKubeconfigFileChangesInjectable;
|
||||||
@ -13,6 +13,7 @@ const startCatalogSyncInjectable = getInjectable({
|
|||||||
const catalogSyncToRenderer = di.inject(catalogSyncToRendererInjectable);
|
const catalogSyncToRenderer = di.inject(catalogSyncToRendererInjectable);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
id: "start-catalog-sync",
|
||||||
run: async () => {
|
run: async () => {
|
||||||
if (!catalogSyncToRenderer.started) {
|
if (!catalogSyncToRenderer.started) {
|
||||||
await catalogSyncToRenderer.start();
|
await catalogSyncToRenderer.start();
|
||||||
|
|||||||
@ -13,6 +13,7 @@ const stopCatalogSyncInjectable = getInjectable({
|
|||||||
const catalogSyncToRenderer = di.inject(catalogSyncToRendererInjectable);
|
const catalogSyncToRenderer = di.inject(catalogSyncToRendererInjectable);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
id: "stop-catalog-sync",
|
||||||
run: async () => {
|
run: async () => {
|
||||||
if (catalogSyncToRenderer.started) {
|
if (catalogSyncToRenderer.started) {
|
||||||
await catalogSyncToRenderer.stop();
|
await catalogSyncToRenderer.stop();
|
||||||
|
|||||||
@ -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 { ClusterManager } from "./cluster-manager";
|
|
||||||
import clusterStoreInjectable from "../common/cluster-store/cluster-store.injectable";
|
|
||||||
import catalogEntityRegistryInjectable from "./catalog/entity-registry.injectable";
|
|
||||||
|
|
||||||
const clusterManagerInjectable = getInjectable({
|
|
||||||
id: "cluster-manager",
|
|
||||||
|
|
||||||
instantiate: (di) => {
|
|
||||||
const clusterManager = new ClusterManager({
|
|
||||||
store: di.inject(clusterStoreInjectable),
|
|
||||||
catalogEntityRegistry: di.inject(catalogEntityRegistryInjectable),
|
|
||||||
});
|
|
||||||
|
|
||||||
clusterManager.init();
|
|
||||||
|
|
||||||
return clusterManager;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default clusterManagerInjectable;
|
|
||||||
14
src/main/cluster/are-being-deleted.injectable.ts
Normal file
14
src/main/cluster/are-being-deleted.injectable.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
/**
|
||||||
|
* 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 { observable } from "mobx";
|
||||||
|
import type { ClusterId } from "../../common/cluster-types";
|
||||||
|
|
||||||
|
const clustersThatAreBeingDeletedInjectable = getInjectable({
|
||||||
|
id: "clusters-that-are-being-deleted",
|
||||||
|
instantiate: () => observable.set<ClusterId>(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default clustersThatAreBeingDeletedInjectable;
|
||||||
25
src/main/cluster/initialize-manager.injectable.ts
Normal file
25
src/main/cluster/initialize-manager.injectable.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 { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import { onLoadOfApplicationInjectionToken } from "../start-main-application/runnable-tokens/on-load-of-application-injection-token";
|
||||||
|
import clusterManagerInjectable from "./manager.injectable";
|
||||||
|
|
||||||
|
const initializeClusterManagerInjectable = getInjectable({
|
||||||
|
id: "initialize-cluster-manager",
|
||||||
|
instantiate: (di) => {
|
||||||
|
const clusterManager = di.inject(clusterManagerInjectable);
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: "initialize-cluster-manager",
|
||||||
|
run: () => {
|
||||||
|
clusterManager.init();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
injectionToken: onLoadOfApplicationInjectionToken,
|
||||||
|
causesSideEffects: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default initializeClusterManagerInjectable;
|
||||||
21
src/main/cluster/manager.injectable.ts
Normal file
21
src/main/cluster/manager.injectable.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
/**
|
||||||
|
* 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 clusterStoreInjectable from "../../common/cluster-store/cluster-store.injectable";
|
||||||
|
import catalogEntityRegistryInjectable from "../catalog/entity-registry.injectable";
|
||||||
|
import clustersThatAreBeingDeletedInjectable from "./are-being-deleted.injectable";
|
||||||
|
import { ClusterManager } from "./manager";
|
||||||
|
|
||||||
|
const clusterManagerInjectable = getInjectable({
|
||||||
|
id: "cluster-manager",
|
||||||
|
|
||||||
|
instantiate: (di) => new ClusterManager({
|
||||||
|
store: di.inject(clusterStoreInjectable),
|
||||||
|
catalogEntityRegistry: di.inject(catalogEntityRegistryInjectable),
|
||||||
|
clustersThatAreBeingDeleted: di.inject(clustersThatAreBeingDeletedInjectable),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default clusterManagerInjectable;
|
||||||
@ -3,36 +3,36 @@
|
|||||||
* 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 "../common/ipc/cluster";
|
import "../../common/ipc/cluster";
|
||||||
import type http from "http";
|
import type http from "http";
|
||||||
|
import type { ObservableSet } from "mobx";
|
||||||
import { action, makeObservable, observable, observe, reaction, toJS } from "mobx";
|
import { action, makeObservable, observable, observe, reaction, toJS } from "mobx";
|
||||||
import type { Cluster } from "../common/cluster/cluster";
|
import type { Cluster } from "../../common/cluster/cluster";
|
||||||
import logger from "./logger";
|
import logger from "../logger";
|
||||||
import { apiKubePrefix } from "../common/vars";
|
import { apiKubePrefix } from "../../common/vars";
|
||||||
import { getClusterIdFromHost, isErrnoException } from "../common/utils";
|
import { getClusterIdFromHost, isErrnoException } from "../../common/utils";
|
||||||
import type { KubernetesClusterPrometheusMetrics } from "../common/catalog-entities/kubernetes-cluster";
|
import type { KubernetesClusterPrometheusMetrics } from "../../common/catalog-entities/kubernetes-cluster";
|
||||||
import { isKubernetesCluster, KubernetesCluster, LensKubernetesClusterStatus } from "../common/catalog-entities/kubernetes-cluster";
|
import { isKubernetesCluster, KubernetesCluster, LensKubernetesClusterStatus } from "../../common/catalog-entities/kubernetes-cluster";
|
||||||
import { ipcMainOn } from "../common/ipc";
|
import { ipcMainOn } from "../../common/ipc";
|
||||||
import { once } from "lodash";
|
import { once } from "lodash";
|
||||||
import type { ClusterStore } from "../common/cluster-store/cluster-store";
|
import type { ClusterStore } from "../../common/cluster-store/cluster-store";
|
||||||
import type { ClusterId } from "../common/cluster-types";
|
import type { ClusterId } from "../../common/cluster-types";
|
||||||
import type { CatalogEntityRegistry } from "./catalog";
|
import type { CatalogEntityRegistry } from "../catalog";
|
||||||
|
|
||||||
const logPrefix = "[CLUSTER-MANAGER]:";
|
const logPrefix = "[CLUSTER-MANAGER]:";
|
||||||
|
|
||||||
const lensSpecificClusterStatuses: Set<string> = new Set(Object.values(LensKubernetesClusterStatus));
|
const lensSpecificClusterStatuses: Set<string> = new Set(Object.values(LensKubernetesClusterStatus));
|
||||||
|
|
||||||
interface Dependencies {
|
interface Dependencies {
|
||||||
store: ClusterStore;
|
readonly store: ClusterStore;
|
||||||
catalogEntityRegistry: CatalogEntityRegistry;
|
readonly catalogEntityRegistry: CatalogEntityRegistry;
|
||||||
|
readonly clustersThatAreBeingDeleted: ObservableSet<ClusterId>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ClusterManager {
|
export class ClusterManager {
|
||||||
deleting = observable.set<ClusterId>();
|
|
||||||
|
|
||||||
@observable visibleCluster: ClusterId | undefined = undefined;
|
@observable visibleCluster: ClusterId | undefined = undefined;
|
||||||
|
|
||||||
constructor(private dependencies: Dependencies) {
|
constructor(private readonly dependencies: Dependencies) {
|
||||||
makeObservable(this);
|
makeObservable(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,7 +69,7 @@ export class ClusterManager {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
observe(this.deleting, change => {
|
observe(this.dependencies.clustersThatAreBeingDeleted, change => {
|
||||||
if (change.type === "add") {
|
if (change.type === "add") {
|
||||||
this.updateEntityStatus(this.dependencies.catalogEntityRegistry.findById(change.newValue) as KubernetesCluster);
|
this.updateEntityStatus(this.dependencies.catalogEntityRegistry.findById(change.newValue) as KubernetesCluster);
|
||||||
}
|
}
|
||||||
@ -141,7 +141,7 @@ export class ClusterManager {
|
|||||||
|
|
||||||
@action
|
@action
|
||||||
protected updateEntityStatus(entity: KubernetesCluster, cluster?: Cluster) {
|
protected updateEntityStatus(entity: KubernetesCluster, cluster?: Cluster) {
|
||||||
if (this.deleting.has(entity.getId())) {
|
if (this.dependencies.clustersThatAreBeingDeleted.has(entity.getId())) {
|
||||||
entity.status.phase = LensKubernetesClusterStatus.DELETING;
|
entity.status.phase = LensKubernetesClusterStatus.DELETING;
|
||||||
entity.status.enabled = false;
|
entity.status.enabled = false;
|
||||||
} else {
|
} else {
|
||||||
@ -94,6 +94,8 @@ export class ContextHandler implements ClusterContextHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected async getPrometheusService(): Promise<PrometheusService> {
|
protected async getPrometheusService(): Promise<PrometheusService> {
|
||||||
|
this.setupPrometheus(this.cluster.preferences);
|
||||||
|
|
||||||
if (this.prometheus && this.prometheusProvider) {
|
if (this.prometheus && this.prometheusProvider) {
|
||||||
return {
|
return {
|
||||||
id: this.prometheusProvider,
|
id: this.prometheusProvider,
|
||||||
|
|||||||
@ -0,0 +1,11 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { getGlobalOverride } from "../../common/test-utils/get-global-override";
|
||||||
|
import randomUUIDInjectable from "./random-uuid.injectable";
|
||||||
|
|
||||||
|
export default getGlobalOverride(randomUUIDInjectable, () => () => {
|
||||||
|
throw new Error("Tried to get a randomUUID without override");
|
||||||
|
});
|
||||||
14
src/main/crypto/random-uuid.injectable.ts
Normal file
14
src/main/crypto/random-uuid.injectable.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
/**
|
||||||
|
* 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 { randomUUID } from "crypto";
|
||||||
|
|
||||||
|
const randomUUIDInjectable = getInjectable({
|
||||||
|
id: "random-uuid",
|
||||||
|
instantiate: () => randomUUID,
|
||||||
|
causesSideEffects: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default randomUUIDInjectable;
|
||||||
@ -13,6 +13,7 @@ const cleanUpDeepLinkingInjectable = getInjectable({
|
|||||||
const lensProtocolRouterMain = di.inject(lensProtocolRouterMainInjectable);
|
const lensProtocolRouterMain = di.inject(lensProtocolRouterMainInjectable);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
id: "clean-up-deep-linking",
|
||||||
run: () => {
|
run: () => {
|
||||||
lensProtocolRouterMain.cleanup();
|
lensProtocolRouterMain.cleanup();
|
||||||
},
|
},
|
||||||
|
|||||||
@ -16,6 +16,7 @@ const hideDockForLastClosedWindowInjectable = getInjectable({
|
|||||||
const getVisibleWindows = di.inject(getVisibleWindowsInjectable);
|
const getVisibleWindows = di.inject(getVisibleWindowsInjectable);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
id: "hide-dock-when-there-are-no-windows",
|
||||||
run: () => {
|
run: () => {
|
||||||
const visibleWindows = getVisibleWindows();
|
const visibleWindows = getVisibleWindows();
|
||||||
|
|
||||||
|
|||||||
@ -13,6 +13,7 @@ const showDockForFirstOpenedWindowInjectable = getInjectable({
|
|||||||
const app = di.inject(electronAppInjectable);
|
const app = di.inject(electronAppInjectable);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
id: "show-dock-for-first-opened-window",
|
||||||
run: () => {
|
run: () => {
|
||||||
app.dock?.show();
|
app.dock?.show();
|
||||||
},
|
},
|
||||||
|
|||||||
@ -15,6 +15,7 @@ const enforceSingleApplicationInstanceInjectable = getInjectable({
|
|||||||
const exitApp = di.inject(exitAppInjectable);
|
const exitApp = di.inject(exitAppInjectable);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
id: "enforce-single-application-instance",
|
||||||
run: () => {
|
run: () => {
|
||||||
if (!requestSingleInstanceLock()) {
|
if (!requestSingleInstanceLock()) {
|
||||||
exitApp();
|
exitApp();
|
||||||
|
|||||||
@ -15,6 +15,7 @@ const setupApplicationNameInjectable = getInjectable({
|
|||||||
const appName = di.inject(appNameInjectable);
|
const appName = di.inject(appNameInjectable);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
id: "setup-application-name",
|
||||||
run: () => {
|
run: () => {
|
||||||
app.setName(appName);
|
app.setName(appName);
|
||||||
},
|
},
|
||||||
|
|||||||
@ -26,6 +26,7 @@ const setupDeepLinkingInjectable = getInjectable({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
id: "setup-deep-linking",
|
||||||
run: async () => {
|
run: async () => {
|
||||||
logger.info(`📟 Setting protocol client for lens://`);
|
logger.info(`📟 Setting protocol client for lens://`);
|
||||||
|
|
||||||
|
|||||||
@ -13,6 +13,7 @@ const setupDeveloperToolsInDevelopmentEnvironmentInjectable = getInjectable({
|
|||||||
const logger = di.inject(loggerInjectable);
|
const logger = di.inject(loggerInjectable);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
id: "setup-developer-tools-in-development-environment",
|
||||||
run: () => {
|
run: () => {
|
||||||
if (process.env.NODE_ENV !== "development") {
|
if (process.env.NODE_ENV !== "development") {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@ -15,6 +15,7 @@ const setupDeviceShutdownInjectable = getInjectable({
|
|||||||
const exitApp = di.inject(exitAppInjectable);
|
const exitApp = di.inject(exitAppInjectable);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
id: "setup-device-shutdown",
|
||||||
run: () => {
|
run: () => {
|
||||||
powerMonitor.on("shutdown", async () => {
|
powerMonitor.on("shutdown", async () => {
|
||||||
exitApp();
|
exitApp();
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable";
|
|||||||
import directoryForLensLocalStorageInjectable from "../../../../common/directory-for-lens-local-storage/directory-for-lens-local-storage.injectable";
|
import directoryForLensLocalStorageInjectable from "../../../../common/directory-for-lens-local-storage/directory-for-lens-local-storage.injectable";
|
||||||
import { setupIpcMainHandlers } from "./setup-ipc-main-handlers";
|
import { setupIpcMainHandlers } from "./setup-ipc-main-handlers";
|
||||||
import loggerInjectable from "../../../../common/logger.injectable";
|
import loggerInjectable from "../../../../common/logger.injectable";
|
||||||
import clusterManagerInjectable from "../../../cluster-manager.injectable";
|
import clusterManagerInjectable from "../../../cluster/manager.injectable";
|
||||||
import applicationMenuItemsInjectable from "../../../menu/application-menu-items.injectable";
|
import applicationMenuItemsInjectable from "../../../menu/application-menu-items.injectable";
|
||||||
import getAbsolutePathInjectable from "../../../../common/path/get-absolute-path.injectable";
|
import getAbsolutePathInjectable from "../../../../common/path/get-absolute-path.injectable";
|
||||||
import clusterStoreInjectable from "../../../../common/cluster-store/cluster-store.injectable";
|
import clusterStoreInjectable from "../../../../common/cluster-store/cluster-store.injectable";
|
||||||
@ -14,6 +14,7 @@ import { onLoadOfApplicationInjectionToken } from "../../../start-main-applicati
|
|||||||
import operatingSystemThemeInjectable from "../../../theme/operating-system-theme.injectable";
|
import operatingSystemThemeInjectable from "../../../theme/operating-system-theme.injectable";
|
||||||
import catalogEntityRegistryInjectable from "../../../catalog/entity-registry.injectable";
|
import catalogEntityRegistryInjectable from "../../../catalog/entity-registry.injectable";
|
||||||
import askUserForFilePathsInjectable from "../../../ipc/ask-user-for-file-paths.injectable";
|
import askUserForFilePathsInjectable from "../../../ipc/ask-user-for-file-paths.injectable";
|
||||||
|
import clustersThatAreBeingDeletedInjectable from "../../../cluster/are-being-deleted.injectable";
|
||||||
|
|
||||||
const setupIpcMainHandlersInjectable = getInjectable({
|
const setupIpcMainHandlersInjectable = getInjectable({
|
||||||
id: "setup-ipc-main-handlers",
|
id: "setup-ipc-main-handlers",
|
||||||
@ -32,8 +33,10 @@ const setupIpcMainHandlersInjectable = getInjectable({
|
|||||||
const clusterStore = di.inject(clusterStoreInjectable);
|
const clusterStore = di.inject(clusterStoreInjectable);
|
||||||
const operatingSystemTheme = di.inject(operatingSystemThemeInjectable);
|
const operatingSystemTheme = di.inject(operatingSystemThemeInjectable);
|
||||||
const askUserForFilePaths = di.inject(askUserForFilePathsInjectable);
|
const askUserForFilePaths = di.inject(askUserForFilePathsInjectable);
|
||||||
|
const clustersThatAreBeingDeleted = di.inject(clustersThatAreBeingDeletedInjectable);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
id: "setup-ipc-main-handlers",
|
||||||
run: () => {
|
run: () => {
|
||||||
logger.debug("[APP-MAIN] initializing ipc main handlers");
|
logger.debug("[APP-MAIN] initializing ipc main handlers");
|
||||||
|
|
||||||
@ -46,6 +49,7 @@ const setupIpcMainHandlersInjectable = getInjectable({
|
|||||||
clusterStore,
|
clusterStore,
|
||||||
operatingSystemTheme,
|
operatingSystemTheme,
|
||||||
askUserForFilePaths,
|
askUserForFilePaths,
|
||||||
|
clustersThatAreBeingDeleted,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -12,10 +12,10 @@ import { appEventBus } from "../../../../common/app-event-bus/event-bus";
|
|||||||
import { broadcastMainChannel, broadcastMessage, ipcMainHandle, ipcMainOn } from "../../../../common/ipc";
|
import { broadcastMainChannel, broadcastMessage, ipcMainHandle, ipcMainOn } from "../../../../common/ipc";
|
||||||
import type { CatalogEntityRegistry } from "../../../catalog";
|
import type { CatalogEntityRegistry } from "../../../catalog";
|
||||||
import { pushCatalogToRenderer } from "../../../catalog-pusher";
|
import { pushCatalogToRenderer } from "../../../catalog-pusher";
|
||||||
import type { ClusterManager } from "../../../cluster-manager";
|
import type { ClusterManager } from "../../../cluster/manager";
|
||||||
import { ResourceApplier } from "../../../resource-applier";
|
import { ResourceApplier } from "../../../resource-applier";
|
||||||
import { remove } from "fs-extra";
|
import { remove } from "fs-extra";
|
||||||
import type { IComputedValue } from "mobx";
|
import type { IComputedValue, ObservableSet } from "mobx";
|
||||||
import type { GetAbsolutePath } from "../../../../common/path/get-absolute-path.injectable";
|
import type { GetAbsolutePath } from "../../../../common/path/get-absolute-path.injectable";
|
||||||
import type { MenuItemOpts } from "../../../menu/application-menu-items.injectable";
|
import type { MenuItemOpts } from "../../../menu/application-menu-items.injectable";
|
||||||
import { windowActionHandleChannel, windowLocationChangedChannel, windowOpenAppMenuAsContextMenuChannel } from "../../../../common/ipc/window";
|
import { windowActionHandleChannel, windowLocationChangedChannel, windowOpenAppMenuAsContextMenuChannel } from "../../../../common/ipc/window";
|
||||||
@ -34,9 +34,20 @@ interface Dependencies {
|
|||||||
clusterStore: ClusterStore;
|
clusterStore: ClusterStore;
|
||||||
operatingSystemTheme: IComputedValue<Theme>;
|
operatingSystemTheme: IComputedValue<Theme>;
|
||||||
askUserForFilePaths: AskUserForFilePaths;
|
askUserForFilePaths: AskUserForFilePaths;
|
||||||
|
clustersThatAreBeingDeleted: ObservableSet<ClusterId>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const setupIpcMainHandlers = ({ applicationMenuItems, directoryForLensLocalStorage, getAbsolutePath, clusterManager, catalogEntityRegistry, clusterStore, operatingSystemTheme, askUserForFilePaths }: Dependencies) => {
|
export const setupIpcMainHandlers = ({
|
||||||
|
applicationMenuItems,
|
||||||
|
directoryForLensLocalStorage,
|
||||||
|
getAbsolutePath,
|
||||||
|
clusterManager,
|
||||||
|
catalogEntityRegistry,
|
||||||
|
clusterStore,
|
||||||
|
operatingSystemTheme,
|
||||||
|
askUserForFilePaths,
|
||||||
|
clustersThatAreBeingDeleted,
|
||||||
|
}: Dependencies) => {
|
||||||
ipcMainHandle(clusterActivateHandler, (event, clusterId: ClusterId, force = false) => {
|
ipcMainHandle(clusterActivateHandler, (event, clusterId: ClusterId, force = false) => {
|
||||||
return ClusterStore.getInstance()
|
return ClusterStore.getInstance()
|
||||||
.getById(clusterId)
|
.getById(clusterId)
|
||||||
@ -101,11 +112,11 @@ export const setupIpcMainHandlers = ({ applicationMenuItems, directoryForLensLoc
|
|||||||
});
|
});
|
||||||
|
|
||||||
ipcMainHandle(clusterSetDeletingHandler, (event, clusterId: string) => {
|
ipcMainHandle(clusterSetDeletingHandler, (event, clusterId: string) => {
|
||||||
clusterManager.deleting.add(clusterId);
|
clustersThatAreBeingDeleted.add(clusterId);
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMainHandle(clusterClearDeletingHandler, (event, clusterId: string) => {
|
ipcMainHandle(clusterClearDeletingHandler, (event, clusterId: string) => {
|
||||||
clusterManager.deleting.delete(clusterId);
|
clustersThatAreBeingDeleted.delete(clusterId);
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMainHandle(clusterKubectlApplyAllHandler, async (event, clusterId: ClusterId, resources: string[], extraArgs: string[]) => {
|
ipcMainHandle(clusterKubectlApplyAllHandler, async (event, clusterId: ClusterId, resources: string[], extraArgs: string[]) => {
|
||||||
|
|||||||
@ -17,6 +17,7 @@ const setupMainWindowVisibilityAfterActivationInjectable = getInjectable({
|
|||||||
const logger = di.inject(loggerInjectable);
|
const logger = di.inject(loggerInjectable);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
id: "setup-main-window-visibility-after-activation",
|
||||||
run: () => {
|
run: () => {
|
||||||
app.on("activate", async (_, windowIsVisible) => {
|
app.on("activate", async (_, windowIsVisible) => {
|
||||||
logger.info("APP:ACTIVATE", { hasVisibleWindows: windowIsVisible });
|
logger.info("APP:ACTIVATE", { hasVisibleWindows: windowIsVisible });
|
||||||
|
|||||||
@ -15,6 +15,7 @@ const setupRunnablesAfterWindowIsOpenedInjectable = getInjectable({
|
|||||||
const afterWindowIsOpened = runManyFor(di)(afterWindowIsOpenedInjectionToken);
|
const afterWindowIsOpened = runManyFor(di)(afterWindowIsOpenedInjectionToken);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
id: "setup-runnables-after-window-is-opened",
|
||||||
run: () => {
|
run: () => {
|
||||||
const app = di.inject(electronAppInjectable);
|
const app = di.inject(electronAppInjectable);
|
||||||
|
|
||||||
|
|||||||
@ -26,6 +26,7 @@ const setupRunnablesBeforeClosingOfApplicationInjectable = getInjectable({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
id: "setup-closing-of-application",
|
||||||
run: () => {
|
run: () => {
|
||||||
const app = di.inject(electronAppInjectable);
|
const app = di.inject(electronAppInjectable);
|
||||||
|
|
||||||
|
|||||||
@ -88,6 +88,9 @@ import { registerMobX } from "@ogre-tools/injectable-extension-for-mobx";
|
|||||||
import electronInjectable from "./utils/resolve-system-proxy/electron.injectable";
|
import electronInjectable from "./utils/resolve-system-proxy/electron.injectable";
|
||||||
import type { HotbarStore } from "../common/hotbars/store";
|
import type { HotbarStore } from "../common/hotbars/store";
|
||||||
import focusApplicationInjectable from "./electron-app/features/focus-application.injectable";
|
import focusApplicationInjectable from "./electron-app/features/focus-application.injectable";
|
||||||
|
import kubectlDownloadingNormalizedArchInjectable from "./kubectl/normalized-arch.injectable";
|
||||||
|
import initializeClusterManagerInjectable from "./cluster/initialize-manager.injectable";
|
||||||
|
import addKubeconfigSyncAsEntitySourceInjectable from "./start-main-application/runnables/kube-config-sync/add-source.injectable";
|
||||||
import type { GlobalOverride } from "../common/test-utils/get-global-override";
|
import type { GlobalOverride } from "../common/test-utils/get-global-override";
|
||||||
|
|
||||||
export function getDiForUnitTesting(opts: { doGeneralOverrides?: boolean } = {}) {
|
export function getDiForUnitTesting(opts: { doGeneralOverrides?: boolean } = {}) {
|
||||||
@ -125,7 +128,7 @@ export function getDiForUnitTesting(opts: { doGeneralOverrides?: boolean } = {})
|
|||||||
di.override(electronInjectable, () => ({}));
|
di.override(electronInjectable, () => ({}));
|
||||||
di.override(waitUntilBundledExtensionsAreLoadedInjectable, () => async () => {});
|
di.override(waitUntilBundledExtensionsAreLoadedInjectable, () => async () => {});
|
||||||
di.override(getRandomIdInjectable, () => () => "some-irrelevant-random-id");
|
di.override(getRandomIdInjectable, () => () => "some-irrelevant-random-id");
|
||||||
|
di.override(kubectlDownloadingNormalizedArchInjectable, () => "amd64");
|
||||||
di.override(hotbarStoreInjectable, () => ({
|
di.override(hotbarStoreInjectable, () => ({
|
||||||
load: () => {},
|
load: () => {},
|
||||||
getActive: () => ({ name: "some-hotbar", items: [] }),
|
getActive: () => ({ name: "some-hotbar", items: [] }),
|
||||||
@ -204,6 +207,8 @@ export function getDiForUnitTesting(opts: { doGeneralOverrides?: boolean } = {})
|
|||||||
const overrideRunnablesHavingSideEffects = (di: DiContainer) => {
|
const overrideRunnablesHavingSideEffects = (di: DiContainer) => {
|
||||||
[
|
[
|
||||||
initializeExtensionsInjectable,
|
initializeExtensionsInjectable,
|
||||||
|
initializeClusterManagerInjectable,
|
||||||
|
addKubeconfigSyncAsEntitySourceInjectable,
|
||||||
setupIpcMainHandlersInjectable,
|
setupIpcMainHandlersInjectable,
|
||||||
setupLensProxyInjectable,
|
setupLensProxyInjectable,
|
||||||
setupShellInjectable,
|
setupShellInjectable,
|
||||||
@ -214,7 +219,10 @@ const overrideRunnablesHavingSideEffects = (di: DiContainer) => {
|
|||||||
startCatalogSyncInjectable,
|
startCatalogSyncInjectable,
|
||||||
startKubeConfigSyncInjectable,
|
startKubeConfigSyncInjectable,
|
||||||
].forEach((injectable) => {
|
].forEach((injectable) => {
|
||||||
di.override(injectable, () => ({ run: () => {} }));
|
di.override(injectable, () => ({
|
||||||
|
id: injectable.id,
|
||||||
|
run: () => {},
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -226,18 +234,20 @@ const overrideOperatingSystem = (di: DiContainer) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const overrideElectronFeatures = (di: DiContainer) => {
|
const overrideElectronFeatures = (di: DiContainer) => {
|
||||||
di.override(setupMainWindowVisibilityAfterActivationInjectable, () => ({
|
[
|
||||||
run: () => {},
|
setupMainWindowVisibilityAfterActivationInjectable,
|
||||||
}));
|
setupDeviceShutdownInjectable,
|
||||||
|
setupDeepLinkingInjectable,
|
||||||
|
setupApplicationNameInjectable,
|
||||||
|
setupRunnablesBeforeClosingOfApplicationInjectable,
|
||||||
|
].forEach((injectable) => {
|
||||||
|
di.override(injectable, () => ({
|
||||||
|
id: injectable.id,
|
||||||
|
run: () => {},
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
di.override(setupDeviceShutdownInjectable, () => ({
|
|
||||||
run: () => {},
|
|
||||||
}));
|
|
||||||
|
|
||||||
di.override(setupDeepLinkingInjectable, () => ({ run: () => {} }));
|
|
||||||
di.override(exitAppInjectable, () => () => {});
|
di.override(exitAppInjectable, () => () => {});
|
||||||
di.override(setupApplicationNameInjectable, () => ({ run: () => {} }));
|
|
||||||
di.override(setupRunnablesBeforeClosingOfApplicationInjectable, () => ({ run: () => {} }));
|
|
||||||
di.override(getCommandLineSwitchInjectable, () => () => "irrelevant");
|
di.override(getCommandLineSwitchInjectable, () => () => "irrelevant");
|
||||||
di.override(requestSingleInstanceLockInjectable, () => () => true);
|
di.override(requestSingleInstanceLockInjectable, () => () => true);
|
||||||
di.override(disableHardwareAccelerationInjectable, () => () => {});
|
di.override(disableHardwareAccelerationInjectable, () => () => {});
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import { LensProxy } from "./lens-proxy";
|
|||||||
import { kubeApiUpgradeRequest } from "./proxy-functions";
|
import { kubeApiUpgradeRequest } from "./proxy-functions";
|
||||||
import routerInjectable from "../router/router.injectable";
|
import routerInjectable from "../router/router.injectable";
|
||||||
import httpProxy from "http-proxy";
|
import httpProxy from "http-proxy";
|
||||||
import clusterManagerInjectable from "../cluster-manager.injectable";
|
import clusterManagerInjectable from "../cluster/manager.injectable";
|
||||||
import shellApiRequestInjectable from "./proxy-functions/shell-api-request/shell-api-request.injectable";
|
import shellApiRequestInjectable from "./proxy-functions/shell-api-request/shell-api-request.injectable";
|
||||||
import lensProxyPortInjectable from "./lens-proxy-port.injectable";
|
import lensProxyPortInjectable from "./lens-proxy-port.injectable";
|
||||||
import contentSecurityPolicyInjectable from "../../common/vars/content-security-policy.injectable";
|
import contentSecurityPolicyInjectable from "../../common/vars/content-security-policy.injectable";
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable";
|
|||||||
import { shellApiRequest } from "./shell-api-request";
|
import { shellApiRequest } from "./shell-api-request";
|
||||||
import createShellSessionInjectable from "../../../shell-session/create-shell-session.injectable";
|
import createShellSessionInjectable from "../../../shell-session/create-shell-session.injectable";
|
||||||
import shellRequestAuthenticatorInjectable from "./shell-request-authenticator/shell-request-authenticator.injectable";
|
import shellRequestAuthenticatorInjectable from "./shell-request-authenticator/shell-request-authenticator.injectable";
|
||||||
import clusterManagerInjectable from "../../../cluster-manager.injectable";
|
import clusterManagerInjectable from "../../../cluster/manager.injectable";
|
||||||
|
|
||||||
const shellApiRequestInjectable = getInjectable({
|
const shellApiRequestInjectable = getInjectable({
|
||||||
id: "shell-api-request",
|
id: "shell-api-request",
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import logger from "../../../logger";
|
|||||||
import type WebSocket from "ws";
|
import type WebSocket from "ws";
|
||||||
import { Server as WebSocketServer } from "ws";
|
import { Server as WebSocketServer } from "ws";
|
||||||
import type { ProxyApiRequestArgs } from "../types";
|
import type { ProxyApiRequestArgs } from "../types";
|
||||||
import type { ClusterManager } from "../../../cluster-manager";
|
import type { ClusterManager } from "../../../cluster/manager";
|
||||||
import URLParse from "url-parse";
|
import URLParse from "url-parse";
|
||||||
import type { Cluster } from "../../../../common/cluster/cluster";
|
import type { Cluster } from "../../../../common/cluster/cluster";
|
||||||
import type { ClusterId } from "../../../../common/cluster-types";
|
import type { ClusterId } from "../../../../common/cluster-types";
|
||||||
|
|||||||
@ -15,6 +15,7 @@ const startApplicationMenuInjectable = getInjectable({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
id: "start-application-menu",
|
||||||
run: async () => {
|
run: async () => {
|
||||||
await applicationMenu.start();
|
await applicationMenu.start();
|
||||||
},
|
},
|
||||||
|
|||||||
@ -15,6 +15,7 @@ const stopApplicationMenuInjectable = getInjectable({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
id: "stop-application-menu",
|
||||||
run: async () => {
|
run: async () => {
|
||||||
await applicationMenu.stop();
|
await applicationMenu.stop();
|
||||||
},
|
},
|
||||||
|
|||||||
@ -10,6 +10,12 @@ import sendToChannelInElectronBrowserWindowInjectable from "./send-to-channel-in
|
|||||||
import type { ElectronWindow } from "./create-lens-window.injectable";
|
import type { ElectronWindow } from "./create-lens-window.injectable";
|
||||||
import type { RequireExactlyOne } from "type-fest";
|
import type { RequireExactlyOne } from "type-fest";
|
||||||
import openLinkInBrowserInjectable from "../../../../common/utils/open-link-in-browser.injectable";
|
import openLinkInBrowserInjectable from "../../../../common/utils/open-link-in-browser.injectable";
|
||||||
|
import getAbsolutePathInjectable from "../../../../common/path/get-absolute-path.injectable";
|
||||||
|
import lensResourcesDirInjectable from "../../../../common/vars/lens-resources-dir.injectable";
|
||||||
|
import isLinuxInjectable from "../../../../common/vars/is-linux.injectable";
|
||||||
|
import fsInjectable from "../../../../common/fs/fs.injectable";
|
||||||
|
import applicationInformationInjectable from "../../../../common/vars/application-information.injectable";
|
||||||
|
|
||||||
|
|
||||||
export type ElectronWindowTitleBarStyle = "hiddenInset" | "hidden" | "default" | "customButtonsOnHover";
|
export type ElectronWindowTitleBarStyle = "hiddenInset" | "hidden" | "default" | "customButtonsOnHover";
|
||||||
|
|
||||||
@ -47,6 +53,10 @@ const createElectronWindowInjectable = getInjectable({
|
|||||||
const logger = di.inject(loggerInjectable);
|
const logger = di.inject(loggerInjectable);
|
||||||
const sendToChannelInLensWindow = di.inject(sendToChannelInElectronBrowserWindowInjectable);
|
const sendToChannelInLensWindow = di.inject(sendToChannelInElectronBrowserWindowInjectable);
|
||||||
const openLinkInBrowser = di.inject(openLinkInBrowserInjectable);
|
const openLinkInBrowser = di.inject(openLinkInBrowserInjectable);
|
||||||
|
const getAbsolutePath = di.inject(getAbsolutePathInjectable);
|
||||||
|
const lensResourcesDir = di.inject(lensResourcesDirInjectable);
|
||||||
|
const isLinux = di.inject(isLinuxInjectable);
|
||||||
|
const applicationInformation = di.inject(applicationInformationInjectable);
|
||||||
|
|
||||||
return (configuration) => {
|
return (configuration) => {
|
||||||
const applicationWindowState = di.inject(
|
const applicationWindowState = di.inject(
|
||||||
@ -81,6 +91,23 @@ const createElectronWindowInjectable = getInjectable({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (isLinux) {
|
||||||
|
const iconFileName = [
|
||||||
|
getAbsolutePath(lensResourcesDir, `../${applicationInformation.name}.png`),
|
||||||
|
`/usr/share/icons/hicolor/512x512/apps/${applicationInformation.name}.png`,
|
||||||
|
].find(di.inject(fsInjectable).existsSync);
|
||||||
|
|
||||||
|
if (iconFileName != null) {
|
||||||
|
try {
|
||||||
|
browserWindow.setIcon(iconFileName);
|
||||||
|
} catch (err) {
|
||||||
|
logger.warn(`Error while setting window icon ${err}`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.warn(`No suitable icon found for task bar.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
applicationWindowState.manage(browserWindow);
|
applicationWindowState.manage(browserWindow);
|
||||||
|
|
||||||
browserWindow
|
browserWindow
|
||||||
|
|||||||
@ -14,6 +14,7 @@ const setupListenerForCurrentClusterFrameInjectable = getInjectable({
|
|||||||
id: "setup-listener-for-current-cluster-frame",
|
id: "setup-listener-for-current-cluster-frame",
|
||||||
|
|
||||||
instantiate: (di) => ({
|
instantiate: (di) => ({
|
||||||
|
id: "setup-listener-for-current-cluster-frame",
|
||||||
run: () => {
|
run: () => {
|
||||||
const currentClusterFrameState = di.inject(currentClusterFrameClusterIdStateInjectable);
|
const currentClusterFrameState = di.inject(currentClusterFrameClusterIdStateInjectable);
|
||||||
|
|
||||||
|
|||||||
@ -10,6 +10,7 @@ const cleanUpShellSessionsInjectable = getInjectable({
|
|||||||
id: "clean-up-shell-sessions",
|
id: "clean-up-shell-sessions",
|
||||||
|
|
||||||
instantiate: () => ({
|
instantiate: () => ({
|
||||||
|
id: "clean-up-shell-sessions",
|
||||||
run: () => {
|
run: () => {
|
||||||
ShellSession.cleanup();
|
ShellSession.cleanup();
|
||||||
},
|
},
|
||||||
|
|||||||
@ -13,6 +13,7 @@ const emitCloseToEventBusInjectable = getInjectable({
|
|||||||
const appEventBus = di.inject(appEventBusInjectable);
|
const appEventBus = di.inject(appEventBusInjectable);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
id: "emit-close-to-event-bus",
|
||||||
run: () => {
|
run: () => {
|
||||||
appEventBus.emit({ name: "app", action: "close" });
|
appEventBus.emit({ name: "app", action: "close" });
|
||||||
},
|
},
|
||||||
|
|||||||
@ -13,6 +13,7 @@ const emitServiceStartToEventBusInjectable = getInjectable({
|
|||||||
const appEventBus = di.inject(appEventBusInjectable);
|
const appEventBus = di.inject(appEventBusInjectable);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
id: "emit-service-start-to-event-bus",
|
||||||
run: () => {
|
run: () => {
|
||||||
appEventBus.emit({ name: "service", action: "start" });
|
appEventBus.emit({ name: "service", action: "start" });
|
||||||
},
|
},
|
||||||
|
|||||||
@ -14,6 +14,7 @@ const flagRendererAsLoadedInjectable = getInjectable({
|
|||||||
const lensProtocolRouterMain = di.inject(lensProtocolRouterMainInjectable);
|
const lensProtocolRouterMain = di.inject(lensProtocolRouterMainInjectable);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
id: "flag-renderer-as-loaded",
|
||||||
run: () => {
|
run: () => {
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
// Todo: remove this kludge which enables out-of-place temporal dependency.
|
// Todo: remove this kludge which enables out-of-place temporal dependency.
|
||||||
|
|||||||
@ -14,6 +14,7 @@ const flagRendererAsNotLoadedInjectable = getInjectable({
|
|||||||
const lensProtocolRouterMain = di.inject(lensProtocolRouterMainInjectable);
|
const lensProtocolRouterMain = di.inject(lensProtocolRouterMainInjectable);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
id: "stop-deep-linking",
|
||||||
run: () => {
|
run: () => {
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
// Todo: remove this kludge which enables out-of-place temporal dependency.
|
// Todo: remove this kludge which enables out-of-place temporal dependency.
|
||||||
|
|||||||
@ -21,6 +21,7 @@ const initializeExtensionsInjectable = getInjectable({
|
|||||||
const showErrorPopup = di.inject(showErrorPopupInjectable);
|
const showErrorPopup = di.inject(showErrorPopupInjectable);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
id: "initialize-extensions",
|
||||||
run: async () => {
|
run: async () => {
|
||||||
logger.info("🧩 Initializing extensions");
|
logger.info("🧩 Initializing extensions");
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,26 @@
|
|||||||
|
/**
|
||||||
|
* 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 kubeconfigSyncManagerInjectable from "../../../catalog-sources/kubeconfig-sync/manager.injectable";
|
||||||
|
import catalogEntityRegistryInjectable from "../../../catalog/entity-registry.injectable";
|
||||||
|
import { afterApplicationIsLoadedInjectionToken } from "../../runnable-tokens/after-application-is-loaded-injection-token";
|
||||||
|
|
||||||
|
const addKubeconfigSyncAsEntitySourceInjectable = getInjectable({
|
||||||
|
id: "add-kubeconfig-sync-as-entity-source",
|
||||||
|
instantiate: (di) => {
|
||||||
|
const kubeConfigSyncManager = di.inject(kubeconfigSyncManagerInjectable);
|
||||||
|
const entityRegistry = di.inject(catalogEntityRegistryInjectable);
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: "add-kubeconfig-sync-as-entity-source",
|
||||||
|
run: () => {
|
||||||
|
entityRegistry.addComputedSource("kubeconfig-sync", kubeConfigSyncManager.source);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
injectionToken: afterApplicationIsLoadedInjectionToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default addKubeconfigSyncAsEntitySourceInjectable;
|
||||||
@ -7,6 +7,7 @@ import { afterApplicationIsLoadedInjectionToken } from "../../runnable-tokens/af
|
|||||||
import directoryForKubeConfigsInjectable from "../../../../common/app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable";
|
import directoryForKubeConfigsInjectable from "../../../../common/app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable";
|
||||||
import ensureDirInjectable from "../../../../common/fs/ensure-dir.injectable";
|
import ensureDirInjectable from "../../../../common/fs/ensure-dir.injectable";
|
||||||
import kubeconfigSyncManagerInjectable from "../../../catalog-sources/kubeconfig-sync/manager.injectable";
|
import kubeconfigSyncManagerInjectable from "../../../catalog-sources/kubeconfig-sync/manager.injectable";
|
||||||
|
import addKubeconfigSyncAsEntitySourceInjectable from "./add-source.injectable";
|
||||||
|
|
||||||
const startKubeConfigSyncInjectable = getInjectable({
|
const startKubeConfigSyncInjectable = getInjectable({
|
||||||
id: "start-kubeconfig-sync",
|
id: "start-kubeconfig-sync",
|
||||||
@ -17,11 +18,13 @@ const startKubeConfigSyncInjectable = getInjectable({
|
|||||||
const ensureDir = di.inject(ensureDirInjectable);
|
const ensureDir = di.inject(ensureDirInjectable);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
id: "start-kubeconfig-sync",
|
||||||
run: async () => {
|
run: async () => {
|
||||||
await ensureDir(directoryForKubeConfigs);
|
await ensureDir(directoryForKubeConfigs);
|
||||||
|
|
||||||
kubeConfigSyncManager.startSync();
|
kubeConfigSyncManager.startSync();
|
||||||
},
|
},
|
||||||
|
runAfter: di.inject(addKubeconfigSyncAsEntitySourceInjectable),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@ -13,6 +13,7 @@ const stopKubeConfigSyncInjectable = getInjectable({
|
|||||||
const kubeConfigSyncManager = di.inject(kubeconfigSyncManagerInjectable);
|
const kubeConfigSyncManager = di.inject(kubeconfigSyncManagerInjectable);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
id: "stop-kube-config-sync",
|
||||||
run: () => {
|
run: () => {
|
||||||
kubeConfigSyncManager.stopSync();
|
kubeConfigSyncManager.stopSync();
|
||||||
},
|
},
|
||||||
|
|||||||
@ -14,6 +14,7 @@ const setupSentryInjectable = getInjectable({
|
|||||||
const initializeSentryOnMain = di.inject(initializeSentryOnMainInjectable);
|
const initializeSentryOnMain = di.inject(initializeSentryOnMainInjectable);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
id: "setup-sentry",
|
||||||
run: () => initializeSentryReportingWith(initializeSentryOnMain),
|
run: () => initializeSentryReportingWith(initializeSentryOnMain),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@ -18,6 +18,7 @@ const setupDetectorRegistryInjectable = getInjectable({
|
|||||||
const detectorRegistry = di.inject(detectorRegistryInjectable);
|
const detectorRegistry = di.inject(detectorRegistryInjectable);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
id: "setup-detector-registry",
|
||||||
run: () => {
|
run: () => {
|
||||||
detectorRegistry
|
detectorRegistry
|
||||||
.add(ClusterIdDetector)
|
.add(ClusterIdDetector)
|
||||||
|
|||||||
@ -15,6 +15,7 @@ const setupHardwareAccelerationInjectable = getInjectable({
|
|||||||
const disableHardwareAcceleration = di.inject(disableHardwareAccelerationInjectable);
|
const disableHardwareAcceleration = di.inject(disableHardwareAccelerationInjectable);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
id: "setup-hardware-acceleration",
|
||||||
run: () => {
|
run: () => {
|
||||||
if (hardwareAccelerationShouldBeDisabled) {
|
if (hardwareAccelerationShouldBeDisabled) {
|
||||||
disableHardwareAcceleration();
|
disableHardwareAcceleration();
|
||||||
|
|||||||
@ -11,6 +11,7 @@ const setupHotbarStoreInjectable = getInjectable({
|
|||||||
id: "setup-hotbar-store",
|
id: "setup-hotbar-store",
|
||||||
|
|
||||||
instantiate: (di) => ({
|
instantiate: (di) => ({
|
||||||
|
id: "setup-hotbar-store",
|
||||||
run: () => {
|
run: () => {
|
||||||
const hotbarStore = di.inject(hotbarStoreInjectable);
|
const hotbarStore = di.inject(hotbarStoreInjectable);
|
||||||
|
|
||||||
|
|||||||
@ -10,6 +10,7 @@ const setupImmerInjectable = getInjectable({
|
|||||||
id: "setup-immer",
|
id: "setup-immer",
|
||||||
|
|
||||||
instantiate: () => ({
|
instantiate: () => ({
|
||||||
|
id: "setup-immer",
|
||||||
run: () => {
|
run: () => {
|
||||||
// Docs: https://immerjs.github.io/immer/
|
// Docs: https://immerjs.github.io/immer/
|
||||||
// Required in `utils/storage-helper.ts`
|
// Required in `utils/storage-helper.ts`
|
||||||
|
|||||||
@ -26,6 +26,7 @@ const setupLensProxyInjectable = getInjectable({
|
|||||||
const buildVersion = di.inject(buildVersionInjectable);
|
const buildVersion = di.inject(buildVersionInjectable);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
id: "setup-lens-proxy",
|
||||||
run: async () => {
|
run: async () => {
|
||||||
try {
|
try {
|
||||||
logger.info("🔌 Starting LensProxy");
|
logger.info("🔌 Starting LensProxy");
|
||||||
|
|||||||
@ -10,6 +10,7 @@ const setupMobxInjectable = getInjectable({
|
|||||||
id: "setup-mobx",
|
id: "setup-mobx",
|
||||||
|
|
||||||
instantiate: () => ({
|
instantiate: () => ({
|
||||||
|
id: "setup-mobx",
|
||||||
run: () => {
|
run: () => {
|
||||||
// Docs: https://mobx.js.org/configuration.html
|
// Docs: https://mobx.js.org/configuration.html
|
||||||
Mobx.configure({
|
Mobx.configure({
|
||||||
|
|||||||
@ -18,6 +18,7 @@ const setupPrometheusRegistryInjectable = getInjectable({
|
|||||||
const prometheusProviderRegistry = di.inject(prometheusProviderRegistryInjectable);
|
const prometheusProviderRegistry = di.inject(prometheusProviderRegistryInjectable);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
id: "setup-prometheus-registry",
|
||||||
run: () => {
|
run: () => {
|
||||||
prometheusProviderRegistry
|
prometheusProviderRegistry
|
||||||
.registerProvider(new PrometheusLens())
|
.registerProvider(new PrometheusLens())
|
||||||
|
|||||||
@ -13,6 +13,7 @@ const setupProxyEnvInjectable = getInjectable({
|
|||||||
const getCommandLineSwitch = di.inject(getCommandLineSwitchInjectable);
|
const getCommandLineSwitch = di.inject(getCommandLineSwitchInjectable);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
id: "setup-proxy-env",
|
||||||
run: () => {
|
run: () => {
|
||||||
const switchValue = getCommandLineSwitch("proxy-server");
|
const switchValue = getCommandLineSwitch("proxy-server");
|
||||||
|
|
||||||
|
|||||||
@ -13,6 +13,7 @@ const setupReactionsInUserStoreInjectable = getInjectable({
|
|||||||
const userStore = di.inject(userStoreInjectable);
|
const userStore = di.inject(userStoreInjectable);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
id: "setup-reactions-in-user-store",
|
||||||
run: () => {
|
run: () => {
|
||||||
userStore.startMainReactions();
|
userStore.startMainReactions();
|
||||||
},
|
},
|
||||||
|
|||||||
@ -15,6 +15,7 @@ const setupSyncingOfGeneralCatalogEntitiesInjectable = getInjectable({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
id: "setup-syncing-of-general-catalog-entities",
|
||||||
run: () => {
|
run: () => {
|
||||||
syncGeneralCatalogEntities();
|
syncGeneralCatalogEntities();
|
||||||
},
|
},
|
||||||
|
|||||||
@ -13,6 +13,7 @@ const setupSyncingOfWeblinksInjectable = getInjectable({
|
|||||||
const syncWeblinks = di.inject(syncWeblinksInjectable);
|
const syncWeblinks = di.inject(syncWeblinksInjectable);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
id: "setup-syncing-of-weblinks",
|
||||||
run: () => {
|
run: () => {
|
||||||
syncWeblinks();
|
syncWeblinks();
|
||||||
},
|
},
|
||||||
|
|||||||
@ -10,6 +10,7 @@ const setupSystemCaInjectable = getInjectable({
|
|||||||
id: "setup-system-ca",
|
id: "setup-system-ca",
|
||||||
|
|
||||||
instantiate: () => ({
|
instantiate: () => ({
|
||||||
|
id: "setup-system-ca",
|
||||||
run: async () => {
|
run: async () => {
|
||||||
await injectSystemCAs();
|
await injectSystemCAs();
|
||||||
},
|
},
|
||||||
|
|||||||
@ -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 clusterManagerInjectable from "../../cluster-manager.injectable";
|
import clusterManagerInjectable from "../../cluster/manager.injectable";
|
||||||
import { beforeQuitOfFrontEndInjectionToken } from "../runnable-tokens/before-quit-of-front-end-injection-token";
|
import { beforeQuitOfFrontEndInjectionToken } from "../runnable-tokens/before-quit-of-front-end-injection-token";
|
||||||
|
|
||||||
const stopClusterManagerInjectable = getInjectable({
|
const stopClusterManagerInjectable = getInjectable({
|
||||||
@ -13,6 +13,7 @@ const stopClusterManagerInjectable = getInjectable({
|
|||||||
const clusterManager = di.inject(clusterManagerInjectable);
|
const clusterManager = di.inject(clusterManagerInjectable);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
id: "stop-cluster-manager",
|
||||||
run: () => {
|
run: () => {
|
||||||
clusterManager.stop();
|
clusterManager.stop();
|
||||||
},
|
},
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import exitAppInjectable from "./electron-app/features/exit-app.injectable";
|
import exitAppInjectable from "./electron-app/features/exit-app.injectable";
|
||||||
import clusterManagerInjectable from "./cluster-manager.injectable";
|
import clusterManagerInjectable from "./cluster/manager.injectable";
|
||||||
import appEventBusInjectable from "../common/app-event-bus/app-event-bus.injectable";
|
import appEventBusInjectable from "../common/app-event-bus/app-event-bus.injectable";
|
||||||
import loggerInjectable from "../common/logger.injectable";
|
import loggerInjectable from "../common/logger.injectable";
|
||||||
import closeAllWindowsInjectable from "./start-main-application/lens-window/hide-all-windows/close-all-windows.injectable";
|
import closeAllWindowsInjectable from "./start-main-application/lens-window/hide-all-windows/close-all-windows.injectable";
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable";
|
|||||||
import userStoreFileNameMigrationInjectable from "../../common/user-store/file-name-migration.injectable";
|
import userStoreFileNameMigrationInjectable from "../../common/user-store/file-name-migration.injectable";
|
||||||
import userStoreInjectable from "../../common/user-store/user-store.injectable";
|
import userStoreInjectable from "../../common/user-store/user-store.injectable";
|
||||||
import { beforeApplicationIsLoadingInjectionToken } from "../start-main-application/runnable-tokens/before-application-is-loading-injection-token";
|
import { beforeApplicationIsLoadingInjectionToken } from "../start-main-application/runnable-tokens/before-application-is-loading-injection-token";
|
||||||
import initDefaultUpdateChannelInjectableInjectable from "../vars/default-update-channel/init.injectable";
|
import initDefaultUpdateChannelInjectable from "../vars/default-update-channel/init.injectable";
|
||||||
|
|
||||||
const initUserStoreInjectable = getInjectable({
|
const initUserStoreInjectable = getInjectable({
|
||||||
id: "init-user-store",
|
id: "init-user-store",
|
||||||
@ -15,11 +15,12 @@ const initUserStoreInjectable = getInjectable({
|
|||||||
const userStoreFileNameMigration = di.inject(userStoreFileNameMigrationInjectable);
|
const userStoreFileNameMigration = di.inject(userStoreFileNameMigrationInjectable);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
id: "init-user-store",
|
||||||
run: async () => {
|
run: async () => {
|
||||||
await userStoreFileNameMigration();
|
await userStoreFileNameMigration();
|
||||||
userStore.load();
|
userStore.load();
|
||||||
},
|
},
|
||||||
runAfter: di.inject(initDefaultUpdateChannelInjectableInjectable),
|
runAfter: di.inject(initDefaultUpdateChannelInjectable),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
injectionToken: beforeApplicationIsLoadingInjectionToken,
|
injectionToken: beforeApplicationIsLoadingInjectionToken,
|
||||||
|
|||||||
@ -13,6 +13,7 @@ const startBroadcastingThemeChangeInjectable = getInjectable({
|
|||||||
const broadcastThemeChange = di.inject(broadcastThemeChangeInjectable);
|
const broadcastThemeChange = di.inject(broadcastThemeChangeInjectable);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
id: "start-broadcasting-theme-change",
|
||||||
run: async () => {
|
run: async () => {
|
||||||
await broadcastThemeChange.start();
|
await broadcastThemeChange.start();
|
||||||
},
|
},
|
||||||
|
|||||||
@ -13,6 +13,7 @@ const stopBroadcastingThemeChangeInjectable = getInjectable({
|
|||||||
const broadcastThemeChange = di.inject(broadcastThemeChangeInjectable);
|
const broadcastThemeChange = di.inject(broadcastThemeChangeInjectable);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
id: "stop-broadcasting-theme-change",
|
||||||
run: async () => {
|
run: async () => {
|
||||||
await broadcastThemeChange.stop();
|
await broadcastThemeChange.stop();
|
||||||
},
|
},
|
||||||
|
|||||||
@ -13,6 +13,7 @@ const startSyncingThemeFromOperatingSystemInjectable = getInjectable({
|
|||||||
const syncTheme = di.inject(syncThemeFromOperatingSystemInjectable);
|
const syncTheme = di.inject(syncThemeFromOperatingSystemInjectable);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
id: "start-syncing-theme-from-operating-system",
|
||||||
run: async () => {
|
run: async () => {
|
||||||
await syncTheme.start();
|
await syncTheme.start();
|
||||||
},
|
},
|
||||||
|
|||||||
@ -13,6 +13,7 @@ const stopSyncingThemeFromOperatingSystemInjectable = getInjectable({
|
|||||||
const syncTheme = di.inject(syncThemeFromOperatingSystemInjectable);
|
const syncTheme = di.inject(syncThemeFromOperatingSystemInjectable);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
id: "stop-syncing-theme-from-operating-system",
|
||||||
run: async () => {
|
run: async () => {
|
||||||
await syncTheme.stop();
|
await syncTheme.stop();
|
||||||
},
|
},
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user