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:
Sebastian Malton authored and GitHub committed 2023-03-28 17:54:25 +03:00
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({
@@ -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({
@@ -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,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();
});
@@ -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;
@@ -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,
});
@@ -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;