mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
cluster-store refactoring -- part 1
Signed-off-by: Roman <ixrock@gmail.com>
This commit is contained in:
1 parent
d4bbaf4f34
commit
afb54694f4
14 files changed
+168
-461
No files matched your search
@@ -25,7 +25,7 @@ export class BaseStore<T = any> extends Singleton {
|
||||
protected constructor(protected params: BaseStoreParams) {
|
||||
super();
|
||||
this.params = {
|
||||
autoLoad: true,
|
||||
autoLoad: !app, // disabled in main process due delayed configuration
|
||||
syncEnabled: true,
|
||||
...params,
|
||||
}
|
||||
@@ -54,7 +54,7 @@ export class BaseStore<T = any> extends Singleton {
|
||||
}
|
||||
}
|
||||
|
||||
protected async load() {
|
||||
async load() {
|
||||
const { configName, syncEnabled, confOptions = {} } = this.params;
|
||||
|
||||
// use "await" to make pseudo-async "load" for more future-proof usages
|
||||
@@ -63,7 +63,10 @@ export class BaseStore<T = any> extends Singleton {
|
||||
projectVersion: getAppVersion(),
|
||||
configName: configName,
|
||||
watch: syncEnabled, // watch for changes in multi-process app (e.g. main/renderer)
|
||||
cwd: (app || remote.app).getPath("userData"), // todo: remove remote.app in favor ipc.invoke
|
||||
get cwd() {
|
||||
// todo: remove remote.app in favor ipc.invoke
|
||||
return (app || remote.app).getPath("userData");
|
||||
},
|
||||
...confOptions,
|
||||
});
|
||||
const data = this.storeConfig.store;
|
||||
|
||||
+68
-70
@@ -1,86 +1,84 @@
|
||||
import Config from "conf"
|
||||
import Singleton from "./utils/singleton";
|
||||
import { action, computed, toJS } from "mobx";
|
||||
import migrations from "../migrations/cluster-store"
|
||||
import { Cluster, ClusterBaseInfo } from "../main/cluster";
|
||||
import { BaseStore } from "./base-store";
|
||||
import { Cluster } from "../main/cluster";
|
||||
|
||||
export class ClusterStore extends Singleton {
|
||||
private storeConfig = new Config({
|
||||
configName: "lens-cluster-store",
|
||||
accessPropertiesByDotNotation: false, // To make dots safe in cluster context names
|
||||
migrations: migrations,
|
||||
})
|
||||
export interface ClusterStoreModel {
|
||||
clusters: ClusterModel[]
|
||||
}
|
||||
|
||||
public getAllClusterObjects(): Cluster[] {
|
||||
return this.storeConfig.get("clusters", []).map((clusterInfo: ClusterBaseInfo) => {
|
||||
return new Cluster(clusterInfo)
|
||||
})
|
||||
export type ClusterId = string;
|
||||
|
||||
export interface ClusterModel {
|
||||
id: ClusterId;
|
||||
contextName: string;
|
||||
kubeConfigPath: string;
|
||||
kubeConfig?: string;
|
||||
port?: number;
|
||||
workspace?: string;
|
||||
preferences?: ClusterPreferences;
|
||||
}
|
||||
|
||||
export interface ClusterPreferences {
|
||||
terminalCWD?: string;
|
||||
clusterName?: string;
|
||||
prometheus?: {
|
||||
namespace: string;
|
||||
service: string;
|
||||
port: number;
|
||||
prefix: string;
|
||||
};
|
||||
prometheusProvider?: {
|
||||
type: string;
|
||||
};
|
||||
icon?: string;
|
||||
httpsProxy?: string;
|
||||
}
|
||||
|
||||
export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
||||
private constructor() {
|
||||
super({
|
||||
configName: "lens-cluster-store",
|
||||
confOptions: {
|
||||
migrations: migrations,
|
||||
accessPropertiesByDotNotation: false, // To make dots safe in cluster context names
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public getAllClusters(): ClusterBaseInfo[] {
|
||||
return this.storeConfig.get("clusters", [])
|
||||
// setup initial value
|
||||
protected data: ClusterStoreModel = {
|
||||
clusters: [],
|
||||
}
|
||||
|
||||
public removeCluster(id: string): void {
|
||||
this.storeConfig.delete(id);
|
||||
const clusterBaseInfos = this.getAllClusters()
|
||||
const index = clusterBaseInfos.findIndex((cbi) => cbi.id === id)
|
||||
if (index !== -1) {
|
||||
clusterBaseInfos.splice(index, 1)
|
||||
this.storeConfig.set("clusters", clusterBaseInfos)
|
||||
@computed get clusters(): Cluster[] {
|
||||
return toJS(this.data.clusters).map(model => new Cluster(model));
|
||||
}
|
||||
|
||||
getById(clusterId: ClusterId): Cluster {
|
||||
return this.clusters.find(cluster => cluster.id === clusterId)
|
||||
}
|
||||
|
||||
getIndexById(clusterId: ClusterId): number {
|
||||
return this.clusters.findIndex(cluster => cluster.id === clusterId)
|
||||
}
|
||||
|
||||
@action
|
||||
removeById(clusterId: ClusterId): void {
|
||||
const index = this.getIndexById(clusterId);
|
||||
if (index > -1) {
|
||||
this.data.clusters.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
public removeClustersByWorkspace(workspace: string) {
|
||||
this.getAllClusters().forEach((cluster) => {
|
||||
if (cluster.workspace === workspace) {
|
||||
this.removeCluster(cluster.id)
|
||||
@action
|
||||
removeAllByWorkspaceId(workspaceId: string) {
|
||||
this.clusters.forEach(cluster => {
|
||||
if (cluster.workspace === workspaceId) {
|
||||
this.removeById(cluster.id)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
public getCluster(id: string): Cluster {
|
||||
const cluster = this.getAllClusterObjects().find((cluster) => cluster.id === id)
|
||||
if (cluster) {
|
||||
return cluster
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
public saveCluster(cluster: ClusterBaseInfo) {
|
||||
const clusters = this.getAllClusters();
|
||||
const index = clusters.findIndex((cl) => cl.id === cluster.id)
|
||||
const storable = {
|
||||
id: cluster.id,
|
||||
kubeConfigPath: cluster.kubeConfigPath,
|
||||
contextName: cluster.contextName,
|
||||
preferences: cluster.preferences,
|
||||
workspace: cluster.workspace
|
||||
}
|
||||
if (index === -1) {
|
||||
clusters.push(storable)
|
||||
} else {
|
||||
clusters[index] = storable
|
||||
}
|
||||
this.storeConfig.set("clusters", clusters)
|
||||
}
|
||||
|
||||
public storeClusters(clusters: ClusterBaseInfo[]) {
|
||||
clusters.forEach((cluster: ClusterBaseInfo) => {
|
||||
this.removeCluster(cluster.id)
|
||||
this.saveCluster(cluster)
|
||||
})
|
||||
}
|
||||
|
||||
public reloadCluster(cluster: ClusterBaseInfo): void {
|
||||
const storedCluster = this.getCluster(cluster.id);
|
||||
if (storedCluster) {
|
||||
cluster.kubeConfigPath = storedCluster.kubeConfigPath
|
||||
cluster.contextName = storedCluster.contextName
|
||||
cluster.preferences = storedCluster.preferences
|
||||
cluster.workspace = storedCluster.workspace
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const clusterStore: ClusterStore = ClusterStore.getInstance();
|
||||
@@ -1,297 +0,0 @@
|
||||
import mockFs from "mock-fs"
|
||||
import yaml from "js-yaml"
|
||||
import { ClusterStore } from "./cluster-store";
|
||||
import { Cluster } from "../main/cluster";
|
||||
|
||||
let clusterStore: ClusterStore;
|
||||
|
||||
beforeEach(() => {
|
||||
ClusterStore.resetInstance()
|
||||
clusterStore = ClusterStore.getInstance();
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
mockFs.restore()
|
||||
})
|
||||
|
||||
describe("for an empty config", () => {
|
||||
beforeEach(() => {
|
||||
const mockOpts = {
|
||||
'tmp': {
|
||||
'lens-cluster-store.json': JSON.stringify({})
|
||||
}
|
||||
}
|
||||
mockFs(mockOpts)
|
||||
})
|
||||
|
||||
it("allows to store and retrieve a cluster", async () => {
|
||||
const cluster = new Cluster({
|
||||
id: 'foo',
|
||||
kubeConfigPath: 'kubeconfig',
|
||||
contextName: "foo",
|
||||
preferences: {
|
||||
terminalCWD: '/tmp',
|
||||
icon: 'path to icon'
|
||||
}
|
||||
})
|
||||
clusterStore.saveCluster(cluster);
|
||||
const storedCluster = clusterStore.getCluster(cluster.id);
|
||||
expect(storedCluster.kubeConfigPath).toBe(cluster.kubeConfigPath)
|
||||
expect(storedCluster.contextName).toBe(cluster.contextName)
|
||||
expect(storedCluster.preferences.icon).toBe(cluster.preferences.icon)
|
||||
expect(storedCluster.preferences.terminalCWD).toBe(cluster.preferences.terminalCWD)
|
||||
expect(storedCluster.id).toBe(cluster.id)
|
||||
})
|
||||
|
||||
it("allows to delete a cluster", async () => {
|
||||
const cluster = new Cluster({
|
||||
id: 'foofoo',
|
||||
kubeConfigPath: 'kubeconfig',
|
||||
contextName: "foo",
|
||||
preferences: {
|
||||
terminalCWD: '/tmp'
|
||||
}
|
||||
})
|
||||
|
||||
clusterStore.saveCluster(cluster);
|
||||
const storedCluster = clusterStore.getCluster(cluster.id);
|
||||
expect(storedCluster.id).toBe(cluster.id)
|
||||
|
||||
clusterStore.removeCluster(cluster.id);
|
||||
|
||||
expect(clusterStore.getCluster(cluster.id)).toBe(null)
|
||||
})
|
||||
})
|
||||
|
||||
describe("for a config with existing clusters", () => {
|
||||
beforeEach(() => {
|
||||
const mockOpts = {
|
||||
'tmp': {
|
||||
'lens-cluster-store.json': JSON.stringify({
|
||||
__internal__: {
|
||||
migrations: {
|
||||
version: "99.99.99"
|
||||
}
|
||||
},
|
||||
clusters: [
|
||||
{
|
||||
id: 'cluster1',
|
||||
kubeConfigPath: 'foo',
|
||||
preferences: { terminalCWD: '/foo' }
|
||||
},
|
||||
{
|
||||
id: 'cluster2',
|
||||
kubeConfigPath: 'foo2',
|
||||
preferences: { terminalCWD: '/foo2' }
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
}
|
||||
mockFs(mockOpts)
|
||||
})
|
||||
|
||||
it("allows to retrieve a cluster", async () => {
|
||||
const storedCluster = clusterStore.getCluster('cluster1')
|
||||
expect(storedCluster.kubeConfigPath).toBe('foo')
|
||||
expect(storedCluster.preferences.terminalCWD).toBe('/foo')
|
||||
expect(storedCluster.id).toBe('cluster1')
|
||||
|
||||
const storedCluster2 = clusterStore.getCluster('cluster2')
|
||||
expect(storedCluster2.kubeConfigPath).toBe('foo2')
|
||||
expect(storedCluster2.preferences.terminalCWD).toBe('/foo2')
|
||||
expect(storedCluster2.id).toBe('cluster2')
|
||||
})
|
||||
|
||||
it("allows to delete a cluster", async () => {
|
||||
clusterStore.removeCluster('cluster2')
|
||||
|
||||
// Verify the other cluster still exists:
|
||||
const storedCluster = clusterStore.getCluster('cluster1')
|
||||
expect(storedCluster.id).toBe('cluster1')
|
||||
|
||||
const storedCluster2 = clusterStore.getCluster('cluster2')
|
||||
expect(storedCluster2).toBe(null)
|
||||
})
|
||||
|
||||
it("allows to reload a cluster in-place", async () => {
|
||||
const cluster = new Cluster({
|
||||
id: 'cluster1',
|
||||
kubeConfigPath: 'kubeconfig string',
|
||||
contextName: "foo",
|
||||
preferences: {
|
||||
terminalCWD: '/tmp'
|
||||
}
|
||||
})
|
||||
|
||||
clusterStore.reloadCluster(cluster)
|
||||
|
||||
expect(cluster.kubeConfigPath).toBe('foo')
|
||||
expect(cluster.preferences.terminalCWD).toBe('/foo')
|
||||
expect(cluster.id).toBe('cluster1')
|
||||
})
|
||||
|
||||
it("allows getting all the clusters", async () => {
|
||||
const storedClusters = clusterStore.getAllClusters()
|
||||
|
||||
expect(storedClusters[0].id).toBe('cluster1')
|
||||
expect(storedClusters[0].preferences.terminalCWD).toBe('/foo')
|
||||
expect(storedClusters[0].kubeConfigPath).toBe('foo')
|
||||
|
||||
expect(storedClusters[1].id).toBe('cluster2')
|
||||
expect(storedClusters[1].preferences.terminalCWD).toBe('/foo2')
|
||||
expect(storedClusters[1].kubeConfigPath).toBe('foo2')
|
||||
})
|
||||
|
||||
it("allows storing the clusters in a different order", async () => {
|
||||
const storedClusters = clusterStore.getAllClusters()
|
||||
|
||||
const reorderedClusters = [storedClusters[1], storedClusters[0]]
|
||||
clusterStore.storeClusters(reorderedClusters)
|
||||
const storedClusters2 = clusterStore.getAllClusters()
|
||||
|
||||
expect(storedClusters2[0].id).toBe('cluster2')
|
||||
expect(storedClusters2[1].id).toBe('cluster1')
|
||||
})
|
||||
})
|
||||
|
||||
// describe("for a pre 2.0 config with an existing cluster", () => {
|
||||
// beforeEach(() => {
|
||||
// const mockOpts = {
|
||||
// 'tmp': {
|
||||
// 'lens-cluster-store.json': JSON.stringify({
|
||||
// __internal__: {
|
||||
// migrations: {
|
||||
// version: "1.0.0"
|
||||
// }
|
||||
// },
|
||||
// cluster1: 'kubeconfig content'
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
// mockFs(mockOpts)
|
||||
// })
|
||||
//
|
||||
// it("migrates to modern format with kubeconfig under a key", async () => {
|
||||
// const storedCluster = clusterStore.store.get('clusters')[0]
|
||||
// expect(storedCluster.kubeConfigPath).toBe(`tmp/kubeconfigs/${storedCluster.id}`)
|
||||
// })
|
||||
// })
|
||||
|
||||
// describe("for a pre 2.4.1 config with an existing cluster", () => {
|
||||
// beforeEach(() => {
|
||||
// const mockOpts = {
|
||||
// 'tmp': {
|
||||
// 'lens-cluster-store.json': JSON.stringify({
|
||||
// __internal__: {
|
||||
// migrations: {
|
||||
// version: "2.0.0-beta.2"
|
||||
// }
|
||||
// },
|
||||
// cluster1: {
|
||||
// kubeConfig: 'foo',
|
||||
// online: true,
|
||||
// accessible: false,
|
||||
// failureReason: 'user error'
|
||||
// },
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
// mockFs(mockOpts)
|
||||
// })
|
||||
//
|
||||
// it("migrates to modern format throwing out the state related data", async () => {
|
||||
// const storedClusterData = clusterStore.store.get('clusters')[0]
|
||||
// expect(storedClusterData.hasOwnProperty('online')).toBe(false)
|
||||
// expect(storedClusterData.hasOwnProperty('accessible')).toBe(false)
|
||||
// expect(storedClusterData.hasOwnProperty('failureReason')).toBe(false)
|
||||
// })
|
||||
// })
|
||||
|
||||
// describe("for a pre 2.6.0 config with a cluster that has arrays in auth config", () => {
|
||||
// beforeEach(() => {
|
||||
// const mockOpts = {
|
||||
// 'tmp': {
|
||||
// 'lens-cluster-store.json': JSON.stringify({
|
||||
// __internal__: {
|
||||
// migrations: {
|
||||
// version: "2.4.1"
|
||||
// }
|
||||
// },
|
||||
// cluster1: {
|
||||
// kubeConfig: "apiVersion: v1\nclusters:\n- cluster:\n server: https://10.211.55.6:8443\n name: minikube\ncontexts:\n- context:\n cluster: minikube\n user: minikube\n name: minikube\ncurrent-context: minikube\nkind: Config\npreferences: {}\nusers:\n- name: minikube\n user:\n client-certificate: /Users/kimmo/.minikube/client.crt\n client-key: /Users/kimmo/.minikube/client.key\n auth-provider:\n config:\n access-token:\n - should be string\n expiry:\n - should be string\n"
|
||||
// },
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
// mockFs(mockOpts)
|
||||
// })
|
||||
//
|
||||
// it("replaces array format access token and expiry into string", async () => {
|
||||
// const storedClusterData = clusterStore.store.get('clusters')[0]
|
||||
// const kc = yaml.safeLoad(fs.readFileSync(storedClusterData.kubeConfigPath).toString())
|
||||
// expect(kc.users[0].user['auth-provider'].config['access-token']).toBe("should be string")
|
||||
// expect(kc.users[0].user['auth-provider'].config['expiry']).toBe("should be string")
|
||||
// expect(storedClusterData.contextName).toBe("minikube")
|
||||
// })
|
||||
// })
|
||||
|
||||
// describe("for a pre 2.6.0 config with a cluster icon", () => {
|
||||
// beforeEach(() => {
|
||||
// const mockOpts = {
|
||||
// 'tmp': {
|
||||
// 'lens-cluster-store.json': JSON.stringify({
|
||||
// __internal__: {
|
||||
// migrations: {
|
||||
// version: "2.4.1"
|
||||
// }
|
||||
// },
|
||||
// cluster1: {
|
||||
// kubeConfig: "foo",
|
||||
// icon: "icon path",
|
||||
// preferences: {
|
||||
// terminalCWD: "/tmp"
|
||||
// }
|
||||
// },
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
// mockFs(mockOpts)
|
||||
// })
|
||||
//
|
||||
// it("moves the icon into preferences", async () => {
|
||||
// const storedClusterData = clusterStore.store.get('clusters')[0]
|
||||
// expect(storedClusterData.hasOwnProperty('icon')).toBe(false)
|
||||
// expect(storedClusterData.preferences.hasOwnProperty('icon')).toBe(true)
|
||||
// expect(storedClusterData.preferences.icon).toBe("icon path")
|
||||
// })
|
||||
// })
|
||||
|
||||
// describe("for a pre 2.7.0-beta.0 config without a workspace", () => {
|
||||
// beforeEach(() => {
|
||||
// const mockOpts = {
|
||||
// 'tmp': {
|
||||
// 'lens-cluster-store.json': JSON.stringify({
|
||||
// __internal__: {
|
||||
// migrations: {
|
||||
// version: "2.6.6"
|
||||
// }
|
||||
// },
|
||||
// cluster1: {
|
||||
// kubeConfig: "foo",
|
||||
// icon: "icon path",
|
||||
// preferences: {
|
||||
// terminalCWD: "/tmp"
|
||||
// }
|
||||
// },
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
// mockFs(mockOpts)
|
||||
// })
|
||||
//
|
||||
// it("adds cluster to default workspace", async () => {
|
||||
// const storedClusterData = clusterStore.store.get("clusters")[0]
|
||||
// expect(storedClusterData.workspace).toBe('default')
|
||||
// })
|
||||
// })
|
||||
@@ -1,5 +1,5 @@
|
||||
import semver from "semver"
|
||||
import { observable, reaction, toJS } from "mobx";
|
||||
import { action, observable, reaction, toJS } from "mobx";
|
||||
import { BaseStore } from "./base-store";
|
||||
import migrations from "../migrations/user-store"
|
||||
import { getAppVersion } from "./utils/app-version";
|
||||
@@ -47,17 +47,19 @@ export class UserStore extends BaseStore<UserStoreModel> {
|
||||
return semver.gt(getAppVersion(), this.lastSeenAppVersion);
|
||||
}
|
||||
|
||||
@action
|
||||
saveLastSeenAppVersion() {
|
||||
this.lastSeenAppVersion = getAppVersion();
|
||||
}
|
||||
|
||||
@action
|
||||
protected fromStore(data: Partial<UserStoreModel> = {}) {
|
||||
const { lastSeenAppVersion, seenContexts, preferences } = data
|
||||
if (lastSeenAppVersion) {
|
||||
this.lastSeenAppVersion = lastSeenAppVersion;
|
||||
}
|
||||
if (seenContexts) {
|
||||
this.seenContexts = observable.set(seenContexts)
|
||||
this.seenContexts = observable.set(seenContexts);
|
||||
}
|
||||
if (preferences) {
|
||||
Object.assign(this.preferences, preferences);
|
||||
@@ -75,5 +77,4 @@ export class UserStore extends BaseStore<UserStoreModel> {
|
||||
}
|
||||
}
|
||||
|
||||
const userStore: UserStore = UserStore.getInstance();
|
||||
export { userStore }
|
||||
export const userStore: UserStore = UserStore.getInstance();
|
||||
@@ -1,4 +1,4 @@
|
||||
import { computed, toJS } from "mobx";
|
||||
import { action, computed, toJS } from "mobx";
|
||||
import { BaseStore } from "./base-store";
|
||||
import { clusterStore } from "./cluster-store"
|
||||
|
||||
@@ -42,6 +42,7 @@ export class WorkspaceStore extends BaseStore<WorkspaceStoreModel> {
|
||||
return this.workspaces.findIndex(workspace => workspace.id === id);
|
||||
}
|
||||
|
||||
@action
|
||||
public saveWorkspace(newWorkspace: Workspace) {
|
||||
const workspace = this.getById(newWorkspace.id);
|
||||
if (workspace) {
|
||||
@@ -51,6 +52,7 @@ export class WorkspaceStore extends BaseStore<WorkspaceStoreModel> {
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
public removeWorkspace(workspaceOrId: Workspace | WorkspaceId) {
|
||||
const workspace = this.getById(typeof workspaceOrId == "string" ? workspaceOrId : workspaceOrId.id);
|
||||
if (!workspace) return;
|
||||
@@ -60,7 +62,7 @@ export class WorkspaceStore extends BaseStore<WorkspaceStoreModel> {
|
||||
const index = this.getIndexById(workspace.id);
|
||||
if (index > -1) {
|
||||
this.data.workspaces.splice(index, 1)
|
||||
clusterStore.removeClustersByWorkspace(workspace.id)
|
||||
clusterStore.removeAllByWorkspaceId(workspace.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user