mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Remove BaseStore<T> and replace with composition of dependencies (#7002)
* Rename ExtensionStore -> BaseExtensionStore - The name was too close to ExtensionsStore Signed-off-by: Sebastian Malton <sebastian@malton.name> * Move ExtensionsStore to new format Signed-off-by: Sebastian Malton <sebastian@malton.name> * Move ClusterStore to new format Signed-off-by: Sebastian Malton <sebastian@malton.name> * Move UserStore to new format Signed-off-by: Sebastian Malton <sebastian@malton.name> * Cleanup types to remove multiple cast locations Signed-off-by: Sebastian Malton <sebastian@malton.name> * Move HotbarStore to new format Signed-off-by: Sebastian Malton <sebastian@malton.name> * Move WeblinkStore to new format Signed-off-by: Sebastian Malton <sebastian@malton.name> * Move FileSystemProvisionerStore to new format Signed-off-by: Sebastian Malton <sebastian@malton.name> * Update snapshots Signed-off-by: Sebastian Malton <sebastian@malton.name> * Clean up impl and rename to better describe intent Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix remaining type errors Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fully split apart the enabled extensions storage Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fully split apart the clusters storage Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fully split apart the hotbar storage Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fully split apart the weblinks storage Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fully split apart the user preferences storage Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix crashing Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix tests and snapshots Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix integration test failures Signed-off-by: Sebastian Malton <sebastian@malton.name> * Improve typing to prevent errors in the future. Signed-off-by: Sebastian Malton <sebastian@malton.name> * Cleanup @k8slens/messaging and friends - To fix type errors Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix lint issue Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix type errors Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix global override not being complete enough causing tests to fail Signed-off-by: Sebastian Malton <sebastian@malton.name> * Bump memory for unit tests on CI Signed-off-by: Sebastian Malton <sebastian@malton.name> * Attempt to fix memory issue on CI again Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fixup test because of new injectables Signed-off-by: Sebastian Malton <sebastian@malton.name> * Upgrade Jest Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix unit tests falling over Signed-off-by: Sebastian Malton <sebastian@malton.name> * Back out jest config change Signed-off-by: Sebastian Malton <sebastian@malton.name> * Remove console log Signed-off-by: Sebastian Malton <sebastian@malton.name> * Update snapshot Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix tests by matching equality instead of snapshots Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix tests by forcing specific snapshot style - Ubuntu CI seems to format arrays in snapshots differently than macOS locally Signed-off-by: Sebastian Malton <sebastian@malton.name> --------- Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
1 parent
ba4a283af9
commit
49f0a1af9c
365 files changed
+6733
-3972
No files matched your search
@@ -3,8 +3,8 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import getClusterByIdInjectable from "../../../../common/cluster-store/get-by-id.injectable";
|
||||
import clusterConnectionInjectable from "../../../../main/cluster/cluster-connection.injectable";
|
||||
import getClusterByIdInjectable from "../../storage/common/get-by-id.injectable";
|
||||
import { requestClusterActivationInjectionToken } from "../common/request-token";
|
||||
|
||||
const requestClusterActivationInjectable = getInjectable({
|
||||
|
||||
+1
-1
@@ -5,8 +5,8 @@
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import emitAppEventInjectable from "../../../../common/app-event-bus/emit-event.injectable";
|
||||
import clusterFramesInjectable from "../../../../common/cluster-frames.injectable";
|
||||
import getClusterByIdInjectable from "../../../../common/cluster-store/get-by-id.injectable";
|
||||
import clusterConnectionInjectable from "../../../../main/cluster/cluster-connection.injectable";
|
||||
import getClusterByIdInjectable from "../../storage/common/get-by-id.injectable";
|
||||
import { requestClusterDeactivationInjectionToken } from "../common/request-token";
|
||||
|
||||
const requestClusterDeactivationInjectable = getInjectable({
|
||||
|
||||
+5
-5
@@ -542,7 +542,7 @@ exports[`Deleting a cluster when an internal kubeconfig cluster is used when the
|
||||
class="HotbarSelector"
|
||||
>
|
||||
<i
|
||||
class="Icon Icon previous material interactive focusable"
|
||||
class="Icon material interactive focusable"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
@@ -1194,7 +1194,7 @@ exports[`Deleting a cluster when the kubeconfig has multiple clusters when the d
|
||||
class="HotbarSelector"
|
||||
>
|
||||
<i
|
||||
class="Icon Icon previous material interactive focusable"
|
||||
class="Icon material interactive focusable"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
@@ -1879,7 +1879,7 @@ exports[`Deleting a cluster when the kubeconfig has multiple clusters when the d
|
||||
class="HotbarSelector"
|
||||
>
|
||||
<i
|
||||
class="Icon Icon previous material interactive focusable"
|
||||
class="Icon material interactive focusable"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
@@ -2643,7 +2643,7 @@ exports[`Deleting a cluster when the kubeconfig has multiple clusters when the d
|
||||
class="HotbarSelector"
|
||||
>
|
||||
<i
|
||||
class="Icon Icon previous material interactive focusable"
|
||||
class="Icon material interactive focusable"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
@@ -3407,7 +3407,7 @@ exports[`Deleting a cluster when the kubeconfig has only one cluster when the di
|
||||
class="HotbarSelector"
|
||||
>
|
||||
<i
|
||||
class="Icon Icon previous material interactive focusable"
|
||||
class="Icon material interactive focusable"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
|
||||
+4
-6
@@ -4,7 +4,6 @@
|
||||
*/
|
||||
import emitAppEventInjectable from "../../../../common/app-event-bus/emit-event.injectable";
|
||||
import clusterFramesInjectable from "../../../../common/cluster-frames.injectable";
|
||||
import clusterStoreInjectable from "../../../../common/cluster-store/cluster-store.injectable";
|
||||
import directoryForLensLocalStorageInjectable from "../../../../common/directory-for-lens-local-storage/directory-for-lens-local-storage.injectable";
|
||||
import removePathInjectable from "../../../../common/fs/remove.injectable";
|
||||
import joinPathsInjectable from "../../../../common/path/join-paths.injectable";
|
||||
@@ -12,22 +11,23 @@ import clusterConnectionInjectable from "../../../../main/cluster/cluster-connec
|
||||
import { noop } from "@k8slens/utilities";
|
||||
import { getRequestChannelListenerInjectable } from "@k8slens/messaging";
|
||||
import { deleteClusterChannel } from "../common/delete-channel";
|
||||
import clustersStateInjectable from "../../storage/common/state.injectable";
|
||||
|
||||
const deleteClusterChannelListenerInjectable = getRequestChannelListenerInjectable({
|
||||
id: "delete-cluster-channel-listener",
|
||||
channel: deleteClusterChannel,
|
||||
getHandler: (di) => {
|
||||
const emitAppEvent = di.inject(emitAppEventInjectable);
|
||||
const clusterStore = di.inject(clusterStoreInjectable);
|
||||
const clusterFrames = di.inject(clusterFramesInjectable);
|
||||
const joinPaths = di.inject(joinPathsInjectable);
|
||||
const directoryForLensLocalStorage = di.inject(directoryForLensLocalStorageInjectable);
|
||||
const deleteFile = di.inject(removePathInjectable);
|
||||
const clustersState = di.inject(clustersStateInjectable);
|
||||
|
||||
return async (clusterId) => {
|
||||
emitAppEvent({ name: "cluster", action: "remove" });
|
||||
|
||||
const cluster = clusterStore.getById(clusterId);
|
||||
const cluster = clustersState.get(clusterId);
|
||||
|
||||
if (!cluster) {
|
||||
return;
|
||||
@@ -37,9 +37,7 @@ const deleteClusterChannelListenerInjectable = getRequestChannelListenerInjectab
|
||||
|
||||
clusterConnection.disconnect();
|
||||
clusterFrames.delete(cluster.id);
|
||||
|
||||
// Remove from the cluster store as well, this should clear any old settings
|
||||
clusterStore.clusters.delete(cluster.id);
|
||||
clustersState.delete(cluster.id);
|
||||
|
||||
// remove the local storage file
|
||||
const localStorageFilePath = joinPaths(directoryForLensLocalStorage, `${cluster.id}.json`);
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
import type { AsyncFnMock } from "@async-fn/jest";
|
||||
import asyncFn from "@async-fn/jest";
|
||||
import type { AuthorizationV1Api, CoreV1Api, V1APIGroupList, V1APIVersions, V1NamespaceList, V1SelfSubjectAccessReview, V1SelfSubjectRulesReview } from "@kubernetes/client-node";
|
||||
import clusterStoreInjectable from "../../common/cluster-store/cluster-store.injectable";
|
||||
import type { Cluster } from "../../common/cluster/cluster";
|
||||
import createAuthorizationApiInjectable from "../../common/cluster/create-authorization-api.injectable";
|
||||
import writeJsonFileInjectable from "../../common/fs/write-json-file.injectable";
|
||||
@@ -26,6 +25,7 @@ import type { KubeAuthProxy } from "../../main/kube-auth-proxy/create-kube-auth-
|
||||
import createKubeAuthProxyInjectable from "../../main/kube-auth-proxy/create-kube-auth-proxy.injectable";
|
||||
import type { Mocked } from "../../test-utils/mock-interface";
|
||||
import { flushPromises } from "@k8slens/test-utils";
|
||||
import addClusterInjectable from "./storage/common/add.injectable";
|
||||
|
||||
describe("Refresh Cluster Accessibility Technical Tests", () => {
|
||||
let builder: ApplicationBuilder;
|
||||
@@ -79,7 +79,7 @@ describe("Refresh Cluster Accessibility Technical Tests", () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
const mainDi = builder.mainDi;
|
||||
const clusterStore = mainDi.inject(clusterStoreInjectable);
|
||||
const addCluster = mainDi.inject(addClusterInjectable);
|
||||
const writeJsonFile = mainDi.inject(writeJsonFileInjectable);
|
||||
|
||||
await writeJsonFile("/some-kube-config-path", {
|
||||
@@ -103,13 +103,11 @@ describe("Refresh Cluster Accessibility Technical Tests", () => {
|
||||
}],
|
||||
});
|
||||
|
||||
clusterStore.addCluster({
|
||||
cluster = addCluster({
|
||||
contextName: "some-cluster-context",
|
||||
id: "some-cluster-id",
|
||||
kubeConfigPath: "/some-kube-config-path",
|
||||
});
|
||||
|
||||
cluster = clusterStore.getById("some-cluster-id") ?? (() => { throw new Error("missing cluster"); })();
|
||||
clusterConnection = mainDi.inject(clusterConnectionInjectable, cluster);
|
||||
refreshPromise = clusterConnection.refreshAccessibilityAndMetadata();
|
||||
});
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { getInjectionToken } from "@ogre-tools/injectable";
|
||||
import type { IComputedValue } from "mobx";
|
||||
import type { KubeApiResourceDescriptor } from "../../../../common/rbac";
|
||||
|
||||
export const shouldShowResourceInjectionToken = getInjectionToken<IComputedValue<boolean>, KubeApiResourceDescriptor>({
|
||||
id: "should-show-resource",
|
||||
});
|
||||
@@ -2,17 +2,17 @@
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import clusterStoreInjectable from "../../../../common/cluster-store/cluster-store.injectable";
|
||||
import { getRequestChannelListenerInjectable } from "@k8slens/messaging";
|
||||
import clustersInjectable from "../../storage/common/clusters.injectable";
|
||||
import { initialClusterStatesChannel } from "../common/channels";
|
||||
|
||||
const handleInitialClusterStateSyncInjectable = getRequestChannelListenerInjectable({
|
||||
id: "handle-initial-cluster-state-sync",
|
||||
channel: initialClusterStatesChannel,
|
||||
getHandler: (di) => {
|
||||
const clusterStore = di.inject(clusterStoreInjectable);
|
||||
const clusters = di.inject(clustersInjectable);
|
||||
|
||||
return () => clusterStore.clustersList.map(cluster => ({
|
||||
return () => clusters.get().map(cluster => ({
|
||||
clusterId: cluster.id,
|
||||
state: cluster.getState(),
|
||||
}));
|
||||
|
||||
@@ -5,22 +5,22 @@
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { isEqual } from "lodash";
|
||||
import { autorun } from "mobx";
|
||||
import clusterStoreInjectable from "../../../../common/cluster-store/cluster-store.injectable";
|
||||
import type { ClusterId, ClusterState } from "../../../../common/cluster-types";
|
||||
import { beforeApplicationIsLoadingInjectionToken } from "@k8slens/application";
|
||||
import initClusterStoreInjectable from "../../store/main/init.injectable";
|
||||
import initClusterStoreInjectable from "../../storage/main/init.injectable";
|
||||
import emitClusterStateUpdateInjectable from "./emit-update.injectable";
|
||||
import clustersInjectable from "../../storage/common/clusters.injectable";
|
||||
|
||||
const setupClusterStateBroadcastingInjectable = getInjectable({
|
||||
id: "setup-cluster-state-broadcasting",
|
||||
instantiate: (di) => ({
|
||||
run: () => {
|
||||
const emitClusterStateUpdate = di.inject(emitClusterStateUpdateInjectable);
|
||||
const clusterStore = di.inject(clusterStoreInjectable);
|
||||
const clusters = di.inject(clustersInjectable);
|
||||
const prevStates = new Map<ClusterId, ClusterState>();
|
||||
|
||||
autorun(() => {
|
||||
for (const cluster of clusterStore.clusters.values()) {
|
||||
for (const cluster of clusters.get()) {
|
||||
const prevState = prevStates.get(cluster.id);
|
||||
const curState = cluster.getState();
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import getClusterByIdInjectable from "../../../../common/cluster-store/get-by-id.injectable";
|
||||
import { getMessageChannelListenerInjectable } from "@k8slens/messaging";
|
||||
import getClusterByIdInjectable from "../../storage/common/get-by-id.injectable";
|
||||
import { clusterStateSyncChannel } from "../common/channels";
|
||||
|
||||
const clusterStateListenerInjectable = getMessageChannelListenerInjectable({
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import getClusterByIdInjectable from "../../../../common/cluster-store/get-by-id.injectable";
|
||||
import { beforeFrameStartsSecondInjectionToken } from "../../../../renderer/before-frame-starts/tokens";
|
||||
import initClusterStoreInjectable from "../../store/renderer/init.injectable";
|
||||
import getClusterByIdInjectable from "../../storage/common/get-by-id.injectable";
|
||||
import initClusterStoreInjectable from "../../storage/renderer/init.injectable";
|
||||
import requestInitialClusterStatesInjectable from "./request-initial.injectable";
|
||||
|
||||
const setupClusterStateSyncInjectable = getInjectable({
|
||||
|
||||
@@ -0,0 +1,375 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { GetCustomKubeConfigFilePath } from "../../../common/app-paths/get-custom-kube-config-directory/get-custom-kube-config-directory.injectable";
|
||||
import getCustomKubeConfigFilePathInjectable from "../../../common/app-paths/get-custom-kube-config-directory/get-custom-kube-config-directory.injectable";
|
||||
import type { DiContainer } from "@ogre-tools/injectable";
|
||||
import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||
import { getDiForUnitTesting } from "../../../main/getDiForUnitTesting";
|
||||
import assert from "assert";
|
||||
import directoryForTempInjectable from "../../../common/app-paths/directory-for-temp/directory-for-temp.injectable";
|
||||
import kubectlBinaryNameInjectable from "../../../main/kubectl/binary-name.injectable";
|
||||
import kubectlDownloadingNormalizedArchInjectable from "../../../main/kubectl/normalized-arch.injectable";
|
||||
import normalizedPlatformInjectable from "../../../common/vars/normalized-platform.injectable";
|
||||
import storeMigrationVersionInjectable from "../../../common/vars/store-migration-version.injectable";
|
||||
import type { WriteJsonSync } from "../../../common/fs/write-json-sync.injectable";
|
||||
import writeJsonSyncInjectable from "../../../common/fs/write-json-sync.injectable";
|
||||
import type { ReadFileSync } from "../../../common/fs/read-file-sync.injectable";
|
||||
import readFileSyncInjectable from "../../../common/fs/read-file-sync.injectable";
|
||||
import { readFileSync } from "fs";
|
||||
import type { WriteFileSync } from "../../../common/fs/write-file-sync.injectable";
|
||||
import writeFileSyncInjectable from "../../../common/fs/write-file-sync.injectable";
|
||||
import type { WriteBufferSync } from "../../../common/fs/write-buffer-sync.injectable";
|
||||
import writeBufferSyncInjectable from "../../../common/fs/write-buffer-sync.injectable";
|
||||
import { Cluster } from "../../../common/cluster/cluster";
|
||||
import clustersPersistentStorageInjectable from "./common/storage.injectable";
|
||||
import type { PersistentStorage } from "../../../common/persistent-storage/create.injectable";
|
||||
import type { AddCluster } from "./common/add.injectable";
|
||||
import addClusterInjectable from "./common/add.injectable";
|
||||
import type { GetClusterById } from "./common/get-by-id.injectable";
|
||||
import getClusterByIdInjectable from "./common/get-by-id.injectable";
|
||||
import type { IComputedValue } from "mobx";
|
||||
import clustersInjectable from "./common/clusters.injectable";
|
||||
|
||||
// NOTE: this is intended to read the actual file system
|
||||
const testDataIcon = readFileSync("test-data/cluster-store-migration-icon.png");
|
||||
const clusterServerUrl = "https://localhost";
|
||||
const kubeconfig = `
|
||||
apiVersion: v1
|
||||
clusters:
|
||||
- cluster:
|
||||
server: ${clusterServerUrl}
|
||||
name: test
|
||||
contexts:
|
||||
- context:
|
||||
cluster: test
|
||||
user: test
|
||||
name: foo
|
||||
- context:
|
||||
cluster: test
|
||||
user: test
|
||||
name: foo2
|
||||
current-context: test
|
||||
kind: Config
|
||||
preferences: {}
|
||||
users:
|
||||
- name: test
|
||||
user:
|
||||
token: kubeconfig-user-q4lm4:xxxyyyy
|
||||
`;
|
||||
|
||||
describe("cluster storage technical tests", () => {
|
||||
let di: DiContainer;
|
||||
let clustersPersistentStorage: PersistentStorage;
|
||||
let writeJsonSync: WriteJsonSync;
|
||||
let writeFileSync: WriteFileSync;
|
||||
let writeBufferSync: WriteBufferSync;
|
||||
let readFileSync: ReadFileSync;
|
||||
let getCustomKubeConfigFilePath: GetCustomKubeConfigFilePath;
|
||||
let writeFileSyncAndReturnPath: (filePath: string, contents: string) => string;
|
||||
let addCluster: AddCluster;
|
||||
let getClusterById: GetClusterById;
|
||||
let clusters: IComputedValue<Cluster[]>;
|
||||
|
||||
beforeEach(async () => {
|
||||
di = getDiForUnitTesting();
|
||||
|
||||
di.override(directoryForUserDataInjectable, () => "/some-directory-for-user-data");
|
||||
di.override(directoryForTempInjectable, () => "/some-temp-directory");
|
||||
di.override(kubectlBinaryNameInjectable, () => "kubectl");
|
||||
di.override(kubectlDownloadingNormalizedArchInjectable, () => "amd64");
|
||||
di.override(normalizedPlatformInjectable, () => "darwin");
|
||||
writeJsonSync = di.inject(writeJsonSyncInjectable);
|
||||
writeFileSync = di.inject(writeFileSyncInjectable);
|
||||
writeBufferSync = di.inject(writeBufferSyncInjectable);
|
||||
readFileSync = di.inject(readFileSyncInjectable);
|
||||
addCluster = di.inject(addClusterInjectable);
|
||||
getClusterById = di.inject(getClusterByIdInjectable);
|
||||
clusters = di.inject(clustersInjectable);
|
||||
writeFileSyncAndReturnPath = (filePath, contents) => (writeFileSync(filePath, contents), filePath);
|
||||
});
|
||||
|
||||
describe("empty config", () => {
|
||||
beforeEach(async () => {
|
||||
getCustomKubeConfigFilePath = di.inject(getCustomKubeConfigFilePathInjectable);
|
||||
|
||||
writeJsonSync("/some-directory-for-user-data/lens-cluster-store.json", {});
|
||||
clustersPersistentStorage = di.inject(clustersPersistentStorageInjectable);
|
||||
clustersPersistentStorage.loadAndStartSyncing();
|
||||
});
|
||||
|
||||
describe("with foo cluster added", () => {
|
||||
beforeEach(() => {
|
||||
const cluster = new Cluster({
|
||||
id: "foo",
|
||||
contextName: "foo",
|
||||
preferences: {
|
||||
terminalCWD: "/some-directory-for-user-data",
|
||||
icon: "data:image/jpeg;base64, iVBORw0KGgoAAAANSUhEUgAAA1wAAAKoCAYAAABjkf5",
|
||||
clusterName: "minikube",
|
||||
},
|
||||
kubeConfigPath: writeFileSyncAndReturnPath(
|
||||
getCustomKubeConfigFilePath("foo"),
|
||||
kubeconfig,
|
||||
),
|
||||
}, {
|
||||
clusterServerUrl,
|
||||
});
|
||||
|
||||
addCluster(cluster);
|
||||
});
|
||||
|
||||
it("adds new cluster to store", async () => {
|
||||
const storedCluster = getClusterById("foo");
|
||||
|
||||
assert(storedCluster);
|
||||
|
||||
expect(storedCluster.id).toBe("foo");
|
||||
expect(storedCluster.preferences.terminalCWD).toBe("/some-directory-for-user-data");
|
||||
expect(storedCluster.preferences.icon).toBe(
|
||||
"data:image/jpeg;base64, iVBORw0KGgoAAAANSUhEUgAAA1wAAAKoCAYAAABjkf5",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("with prod and dev clusters added", () => {
|
||||
beforeEach(() => {
|
||||
addCluster({
|
||||
id: "prod",
|
||||
contextName: "foo",
|
||||
preferences: {
|
||||
clusterName: "prod",
|
||||
},
|
||||
kubeConfigPath: writeFileSyncAndReturnPath(
|
||||
getCustomKubeConfigFilePath("prod"),
|
||||
kubeconfig,
|
||||
),
|
||||
});
|
||||
addCluster({
|
||||
id: "dev",
|
||||
contextName: "foo2",
|
||||
preferences: {
|
||||
clusterName: "dev",
|
||||
},
|
||||
kubeConfigPath: writeFileSyncAndReturnPath(
|
||||
getCustomKubeConfigFilePath("dev"),
|
||||
kubeconfig,
|
||||
),
|
||||
});
|
||||
});
|
||||
|
||||
it("check if store can contain multiple clusters", () => {
|
||||
expect(clusters.get().length).toBe(2);
|
||||
});
|
||||
|
||||
it("check if cluster's kubeconfig file saved", () => {
|
||||
const file = writeFileSyncAndReturnPath(getCustomKubeConfigFilePath("boo"), "kubeconfig");
|
||||
|
||||
expect(readFileSync(file)).toBe("kubeconfig");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("config with existing clusters", () => {
|
||||
beforeEach(() => {
|
||||
writeFileSync("/temp-kube-config", kubeconfig);
|
||||
writeJsonSync("/some-directory-for-user-data/lens-cluster-store.json", {
|
||||
__internal__: {
|
||||
migrations: {
|
||||
version: "99.99.99",
|
||||
},
|
||||
},
|
||||
clusters: [
|
||||
{
|
||||
id: "cluster1",
|
||||
kubeConfigPath: "/temp-kube-config",
|
||||
contextName: "foo",
|
||||
preferences: { terminalCWD: "/foo" },
|
||||
workspace: "default",
|
||||
},
|
||||
{
|
||||
id: "cluster2",
|
||||
kubeConfigPath: "/temp-kube-config",
|
||||
contextName: "foo2",
|
||||
preferences: { terminalCWD: "/foo2" },
|
||||
},
|
||||
{
|
||||
id: "cluster3",
|
||||
kubeConfigPath: "/temp-kube-config",
|
||||
contextName: "foo",
|
||||
preferences: { terminalCWD: "/foo" },
|
||||
workspace: "foo",
|
||||
ownerRef: "foo",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
getCustomKubeConfigFilePath = di.inject(getCustomKubeConfigFilePathInjectable);
|
||||
|
||||
clustersPersistentStorage = di.inject(clustersPersistentStorageInjectable);
|
||||
clustersPersistentStorage.loadAndStartSyncing();
|
||||
});
|
||||
it("allows to retrieve a cluster", () => {
|
||||
const storedCluster = getClusterById("cluster1");
|
||||
|
||||
assert(storedCluster);
|
||||
|
||||
expect(storedCluster.id).toBe("cluster1");
|
||||
expect(storedCluster.preferences.terminalCWD).toBe("/foo");
|
||||
});
|
||||
|
||||
it("allows getting all of the clusters", async () => {
|
||||
const storedClusters = clusters.get();
|
||||
|
||||
expect(storedClusters.length).toBe(3);
|
||||
expect(storedClusters[0].id).toBe("cluster1");
|
||||
expect(storedClusters[0].preferences.terminalCWD).toBe("/foo");
|
||||
expect(storedClusters[1].id).toBe("cluster2");
|
||||
expect(storedClusters[1].preferences.terminalCWD).toBe("/foo2");
|
||||
expect(storedClusters[2].id).toBe("cluster3");
|
||||
});
|
||||
});
|
||||
|
||||
describe("config with invalid cluster kubeconfig", () => {
|
||||
beforeEach(() => {
|
||||
writeFileSync("/invalid-kube-config", invalidKubeconfig);
|
||||
writeFileSync("/valid-kube-config", kubeconfig);
|
||||
writeJsonSync("/some-directory-for-user-data/lens-cluster-store.json", {
|
||||
__internal__: {
|
||||
migrations: {
|
||||
version: "99.99.99",
|
||||
},
|
||||
},
|
||||
clusters: [
|
||||
{
|
||||
id: "cluster1",
|
||||
kubeConfigPath: "/invalid-kube-config",
|
||||
contextName: "test",
|
||||
preferences: { terminalCWD: "/foo" },
|
||||
workspace: "foo",
|
||||
},
|
||||
{
|
||||
id: "cluster2",
|
||||
kubeConfigPath: "/valid-kube-config",
|
||||
contextName: "foo",
|
||||
preferences: { terminalCWD: "/foo" },
|
||||
workspace: "default",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
getCustomKubeConfigFilePath = di.inject(getCustomKubeConfigFilePathInjectable);
|
||||
|
||||
clustersPersistentStorage = di.inject(clustersPersistentStorageInjectable);
|
||||
clustersPersistentStorage.loadAndStartSyncing();
|
||||
});
|
||||
|
||||
it("does not enable clusters with invalid kubeconfig", () => {
|
||||
const storedClusters = clusters.get();
|
||||
|
||||
expect(storedClusters.length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("pre 3.6.0-beta.1 config with an existing cluster", () => {
|
||||
beforeEach(() => {
|
||||
di.override(storeMigrationVersionInjectable, () => "3.6.0");
|
||||
|
||||
getCustomKubeConfigFilePath = di.inject(getCustomKubeConfigFilePathInjectable);
|
||||
|
||||
writeJsonSync("/some-directory-for-user-data/lens-cluster-store.json", {
|
||||
__internal__: {
|
||||
migrations: {
|
||||
version: "3.5.0",
|
||||
},
|
||||
},
|
||||
clusters: [
|
||||
{
|
||||
id: "cluster1",
|
||||
kubeConfig: minimalValidKubeConfig,
|
||||
contextName: "cluster",
|
||||
preferences: {
|
||||
icon: "store://icon_path",
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
writeBufferSync("/some-directory-for-user-data/icon_path", testDataIcon);
|
||||
|
||||
|
||||
clustersPersistentStorage = di.inject(clustersPersistentStorageInjectable);
|
||||
clustersPersistentStorage.loadAndStartSyncing();
|
||||
});
|
||||
|
||||
it("migrates to modern format with kubeconfig in a file", async () => {
|
||||
const configPath = clusters.get()[0].kubeConfigPath.get();
|
||||
|
||||
expect(readFileSync(configPath)).toBe(minimalValidKubeConfig);
|
||||
});
|
||||
|
||||
it("migrates to modern format with icon not in file", async () => {
|
||||
expect(clusters.get()[0].preferences.icon).toMatch(/data:;base64,/);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const invalidKubeconfig = JSON.stringify({
|
||||
apiVersion: "v1",
|
||||
clusters: [{
|
||||
cluster: {
|
||||
server: "https://localhost",
|
||||
},
|
||||
name: "test2",
|
||||
}],
|
||||
contexts: [{
|
||||
context: {
|
||||
cluster: "test",
|
||||
user: "test",
|
||||
},
|
||||
name: "test",
|
||||
}],
|
||||
"current-context": "test",
|
||||
kind: "Config",
|
||||
preferences: {},
|
||||
users: [{
|
||||
user: {
|
||||
token: "kubeconfig-user-q4lm4:xxxyyyy",
|
||||
},
|
||||
name: "test",
|
||||
}],
|
||||
});
|
||||
|
||||
const minimalValidKubeConfig = JSON.stringify({
|
||||
apiVersion: "v1",
|
||||
clusters: [
|
||||
{
|
||||
name: "minikube",
|
||||
cluster: {
|
||||
server: "https://192.168.64.3:8443",
|
||||
},
|
||||
},
|
||||
],
|
||||
"current-context": "minikube",
|
||||
contexts: [
|
||||
{
|
||||
context: {
|
||||
cluster: "minikube",
|
||||
user: "minikube",
|
||||
},
|
||||
name: "minikube",
|
||||
},
|
||||
],
|
||||
users: [
|
||||
{
|
||||
name: "minikube",
|
||||
user: {
|
||||
"client-certificate": "/Users/foo/.minikube/client.crt",
|
||||
"client-key": "/Users/foo/.minikube/client.key",
|
||||
},
|
||||
},
|
||||
],
|
||||
kind: "Config",
|
||||
preferences: {},
|
||||
});
|
||||
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* 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 { action } from "mobx";
|
||||
import emitAppEventInjectable from "../../../../common/app-event-bus/emit-event.injectable";
|
||||
import readClusterConfigSyncInjectable from "./read-cluster-config.injectable";
|
||||
import type { ClusterModel } from "../../../../common/cluster-types";
|
||||
import { Cluster } from "../../../../common/cluster/cluster";
|
||||
import clustersStateInjectable from "./state.injectable";
|
||||
|
||||
export type AddCluster = (clusterOrModel: ClusterModel | Cluster) => Cluster;
|
||||
|
||||
const addClusterInjectable = getInjectable({
|
||||
id: "add-cluster",
|
||||
instantiate: (di): AddCluster => {
|
||||
const clustersState = di.inject(clustersStateInjectable);
|
||||
const emitAppEvent = di.inject(emitAppEventInjectable);
|
||||
const readClusterConfigSync = di.inject(readClusterConfigSyncInjectable);
|
||||
|
||||
return action((clusterOrModel) => {
|
||||
emitAppEvent({ name: "cluster", action: "add" });
|
||||
|
||||
const cluster = clusterOrModel instanceof Cluster
|
||||
? clusterOrModel
|
||||
: new Cluster(
|
||||
clusterOrModel,
|
||||
readClusterConfigSync(clusterOrModel),
|
||||
);
|
||||
|
||||
clustersState.set(cluster.id, cluster);
|
||||
|
||||
return cluster;
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export default addClusterInjectable;
|
||||
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* 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 { computed } from "mobx";
|
||||
import clustersStateInjectable from "./state.injectable";
|
||||
|
||||
const clustersInjectable = getInjectable({
|
||||
id: "clusters",
|
||||
instantiate: (di) => {
|
||||
const clustersState = di.inject(clustersStateInjectable);
|
||||
|
||||
return computed(() => [...clustersState.values()]);
|
||||
},
|
||||
});
|
||||
|
||||
export default clustersInjectable;
|
||||
@@ -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 "../../../../common/cluster-types";
|
||||
import type { Cluster } from "../../../../common/cluster/cluster";
|
||||
import clustersStateInjectable from "./state.injectable";
|
||||
|
||||
export type GetClusterById = (id: ClusterId) => Cluster | undefined;
|
||||
|
||||
const getClusterByIdInjectable = getInjectable({
|
||||
id: "get-cluster-by-id",
|
||||
instantiate: (di): GetClusterById => {
|
||||
const clustersState = di.inject(clustersStateInjectable);
|
||||
|
||||
return (id) => clustersState.get(id);
|
||||
},
|
||||
});
|
||||
|
||||
export default getClusterByIdInjectable;
|
||||
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { getInjectionToken } from "@ogre-tools/injectable";
|
||||
import type { MigrationDeclaration } from "../../../../common/persistent-storage/migrations.injectable";
|
||||
|
||||
export const clusterStoreMigrationInjectionToken = getInjectionToken<MigrationDeclaration>({
|
||||
id: "cluster-store-migration",
|
||||
});
|
||||
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* 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 { ClusterConfigData, ClusterModel } from "../../../../common/cluster-types";
|
||||
import readFileSyncInjectable from "../../../../common/fs/read-file-sync.injectable";
|
||||
import { loadConfigFromString, validateKubeConfig } from "../../../../common/kube-helpers";
|
||||
|
||||
export type ReadClusterConfigSync = (model: ClusterModel) => ClusterConfigData;
|
||||
|
||||
const readClusterConfigSyncInjectable = getInjectable({
|
||||
id: "read-cluster-config-sync",
|
||||
instantiate: (di): ReadClusterConfigSync => {
|
||||
const readFileSync = di.inject(readFileSyncInjectable);
|
||||
|
||||
return ({ kubeConfigPath, contextName }) => {
|
||||
const kubeConfigData = readFileSync(kubeConfigPath);
|
||||
const { config } = loadConfigFromString(kubeConfigData);
|
||||
const result = validateKubeConfig(config, contextName);
|
||||
|
||||
if (result.error) {
|
||||
throw result.error;
|
||||
}
|
||||
|
||||
return { clusterServerUrl: result.cluster.server };
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default readClusterConfigSyncInjectable;
|
||||
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* 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";
|
||||
import type { Cluster } from "../../../../common/cluster/cluster";
|
||||
|
||||
const clustersStateInjectable = getInjectable({
|
||||
id: "clusters-state",
|
||||
instantiate: () => observable.map<ClusterId, Cluster>(),
|
||||
});
|
||||
|
||||
export default clustersStateInjectable;
|
||||
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { iter } from "@k8slens/utilities";
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { comparer, action } from "mobx";
|
||||
import { clusterStoreMigrationInjectionToken } from "./migration-token";
|
||||
import readClusterConfigSyncInjectable from "./read-cluster-config.injectable";
|
||||
import type { ClusterId, ClusterModel } from "../../../../common/cluster-types";
|
||||
import { Cluster } from "../../../../common/cluster/cluster";
|
||||
import loggerInjectable from "../../../../common/logger.injectable";
|
||||
import createPersistentStorageInjectable from "../../../../common/persistent-storage/create.injectable";
|
||||
import persistentStorageMigrationsInjectable from "../../../../common/persistent-storage/migrations.injectable";
|
||||
import storeMigrationVersionInjectable from "../../../../common/vars/store-migration-version.injectable";
|
||||
import clustersStateInjectable from "./state.injectable";
|
||||
|
||||
export interface ClusterStoreModel {
|
||||
clusters?: ClusterModel[];
|
||||
}
|
||||
|
||||
const clustersPersistentStorageInjectable = getInjectable({
|
||||
id: "clusters-persistent-storage",
|
||||
instantiate: (di) => {
|
||||
const createPersistentStorage = di.inject(createPersistentStorageInjectable);
|
||||
const readClusterConfigSync = di.inject(readClusterConfigSyncInjectable);
|
||||
const clustersState = di.inject(clustersStateInjectable);
|
||||
const logger = di.inject(loggerInjectable);
|
||||
|
||||
return createPersistentStorage<ClusterStoreModel>({
|
||||
configName: "lens-cluster-store",
|
||||
accessPropertiesByDotNotation: false, // To make dots safe in cluster context names
|
||||
syncOptions: {
|
||||
equals: comparer.structural,
|
||||
},
|
||||
projectVersion: di.inject(storeMigrationVersionInjectable),
|
||||
migrations: di.inject(persistentStorageMigrationsInjectable, clusterStoreMigrationInjectionToken),
|
||||
fromStore: action(({ clusters = [] }) => {
|
||||
const currentClusters = new Map(clustersState);
|
||||
const newClusters = new Map<ClusterId, Cluster>();
|
||||
|
||||
// update new clusters
|
||||
for (const clusterModel of clusters) {
|
||||
try {
|
||||
let cluster = currentClusters.get(clusterModel.id);
|
||||
|
||||
if (cluster) {
|
||||
cluster.updateModel(clusterModel);
|
||||
} else {
|
||||
cluster = new Cluster(
|
||||
clusterModel,
|
||||
readClusterConfigSync(clusterModel),
|
||||
);
|
||||
}
|
||||
newClusters.set(clusterModel.id, cluster);
|
||||
} catch (error) {
|
||||
logger.warn(`[CLUSTER-STORE]: Failed to update/create a cluster: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
clustersState.replace(newClusters);
|
||||
}),
|
||||
toJSON: () => ({
|
||||
clusters: iter.chain(clustersState.values())
|
||||
.map(cluster => cluster.toJSON())
|
||||
.toArray(),
|
||||
}),
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export default clustersPersistentStorageInjectable;
|
||||
+5
-5
@@ -3,19 +3,19 @@
|
||||
* 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 { beforeApplicationIsLoadingInjectionToken } from "@k8slens/application";
|
||||
import initUserStoreInjectable from "../../../../main/stores/init-user-store.injectable";
|
||||
import clustersPersistentStorageInjectable from "../common/storage.injectable";
|
||||
import loadUserPreferencesStorageInjectable from "../../../user-preferences/main/load-storage.injectable";
|
||||
|
||||
const initClusterStoreInjectable = getInjectable({
|
||||
id: "init-cluster-store",
|
||||
instantiate: (di) => ({
|
||||
run: () => {
|
||||
const clusterStore = di.inject(clusterStoreInjectable);
|
||||
const storage = di.inject(clustersPersistentStorageInjectable);
|
||||
|
||||
clusterStore.load();
|
||||
storage.loadAndStartSyncing();
|
||||
},
|
||||
runAfter: initUserStoreInjectable,
|
||||
runAfter: loadUserPreferencesStorageInjectable,
|
||||
}),
|
||||
injectionToken: beforeApplicationIsLoadingInjectionToken,
|
||||
});
|
||||
+4
-4
@@ -3,17 +3,17 @@
|
||||
* 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 { beforeFrameStartsSecondInjectionToken } from "../../../../renderer/before-frame-starts/tokens";
|
||||
import initUserStoreInjectable from "../../../../renderer/stores/init-user-store.injectable";
|
||||
import initUserStoreInjectable from "../../../user-preferences/renderer/load-storage.injectable";
|
||||
import clustersPersistentStorageInjectable from "../common/storage.injectable";
|
||||
|
||||
const initClusterStoreInjectable = getInjectable({
|
||||
id: "init-cluster-store",
|
||||
instantiate: (di) => ({
|
||||
run: () => {
|
||||
const clusterStore = di.inject(clusterStoreInjectable);
|
||||
const storage = di.inject(clustersPersistentStorageInjectable);
|
||||
|
||||
clusterStore.load();
|
||||
storage.loadAndStartSyncing();
|
||||
},
|
||||
runAfter: initUserStoreInjectable,
|
||||
}),
|
||||
@@ -13,7 +13,7 @@ import { frontEndRouteInjectionToken } from "../../common/front-end-routing/fron
|
||||
import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
|
||||
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
|
||||
import { navigateToRouteInjectionToken } from "../../common/front-end-routing/navigate-to-route-injection-token";
|
||||
import { shouldShowResourceInjectionToken } from "../../common/cluster-store/allowed-resources-injection-token";
|
||||
import { shouldShowResourceInjectionToken } from "./showing-kube-resources/common/allowed-resources-injection-token";
|
||||
|
||||
describe("cluster - visibility of sidebar items", () => {
|
||||
let builder: ApplicationBuilder;
|
||||
|
||||
Reference in new issue
Block a user