1
0
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:
Roman 2020-07-06 14:31:34 +03:00
parent d4bbaf4f34
commit afb54694f4
14 changed files with 168 additions and 461 deletions

View File

@ -25,7 +25,7 @@ export class BaseStore<T = any> extends Singleton {
protected constructor(protected params: BaseStoreParams) { protected constructor(protected params: BaseStoreParams) {
super(); super();
this.params = { this.params = {
autoLoad: true, autoLoad: !app, // disabled in main process due delayed configuration
syncEnabled: true, syncEnabled: true,
...params, ...params,
} }
@ -54,7 +54,7 @@ export class BaseStore<T = any> extends Singleton {
} }
} }
protected async load() { async load() {
const { configName, syncEnabled, confOptions = {} } = this.params; const { configName, syncEnabled, confOptions = {} } = this.params;
// use "await" to make pseudo-async "load" for more future-proof usages // 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(), projectVersion: getAppVersion(),
configName: configName, configName: configName,
watch: syncEnabled, // watch for changes in multi-process app (e.g. main/renderer) 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, ...confOptions,
}); });
const data = this.storeConfig.store; const data = this.storeConfig.store;

View File

@ -1,86 +1,84 @@
import Config from "conf" import { action, computed, toJS } from "mobx";
import Singleton from "./utils/singleton";
import migrations from "../migrations/cluster-store" 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 { export interface ClusterStoreModel {
private storeConfig = new Config({ clusters: ClusterModel[]
configName: "lens-cluster-store", }
accessPropertiesByDotNotation: false, // To make dots safe in cluster context names
migrations: migrations,
})
public getAllClusterObjects(): Cluster[] { export type ClusterId = string;
return this.storeConfig.get("clusters", []).map((clusterInfo: ClusterBaseInfo) => {
return new Cluster(clusterInfo) 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[] { // setup initial value
return this.storeConfig.get("clusters", []) protected data: ClusterStoreModel = {
clusters: [],
} }
public removeCluster(id: string): void { @computed get clusters(): Cluster[] {
this.storeConfig.delete(id); return toJS(this.data.clusters).map(model => new Cluster(model));
const clusterBaseInfos = this.getAllClusters() }
const index = clusterBaseInfos.findIndex((cbi) => cbi.id === id)
if (index !== -1) { getById(clusterId: ClusterId): Cluster {
clusterBaseInfos.splice(index, 1) return this.clusters.find(cluster => cluster.id === clusterId)
this.storeConfig.set("clusters", clusterBaseInfos) }
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) { @action
this.getAllClusters().forEach((cluster) => { removeAllByWorkspaceId(workspaceId: string) {
if (cluster.workspace === workspace) { this.clusters.forEach(cluster => {
this.removeCluster(cluster.id) 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(); export const clusterStore: ClusterStore = ClusterStore.getInstance();

View File

@ -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')
// })
// })

View File

@ -1,5 +1,5 @@
import semver from "semver" import semver from "semver"
import { observable, reaction, toJS } from "mobx"; import { action, observable, reaction, toJS } from "mobx";
import { BaseStore } from "./base-store"; import { BaseStore } from "./base-store";
import migrations from "../migrations/user-store" import migrations from "../migrations/user-store"
import { getAppVersion } from "./utils/app-version"; import { getAppVersion } from "./utils/app-version";
@ -47,17 +47,19 @@ export class UserStore extends BaseStore<UserStoreModel> {
return semver.gt(getAppVersion(), this.lastSeenAppVersion); return semver.gt(getAppVersion(), this.lastSeenAppVersion);
} }
@action
saveLastSeenAppVersion() { saveLastSeenAppVersion() {
this.lastSeenAppVersion = getAppVersion(); this.lastSeenAppVersion = getAppVersion();
} }
@action
protected fromStore(data: Partial<UserStoreModel> = {}) { protected fromStore(data: Partial<UserStoreModel> = {}) {
const { lastSeenAppVersion, seenContexts, preferences } = data const { lastSeenAppVersion, seenContexts, preferences } = data
if (lastSeenAppVersion) { if (lastSeenAppVersion) {
this.lastSeenAppVersion = lastSeenAppVersion; this.lastSeenAppVersion = lastSeenAppVersion;
} }
if (seenContexts) { if (seenContexts) {
this.seenContexts = observable.set(seenContexts) this.seenContexts = observable.set(seenContexts);
} }
if (preferences) { if (preferences) {
Object.assign(this.preferences, preferences); Object.assign(this.preferences, preferences);
@ -75,5 +77,4 @@ export class UserStore extends BaseStore<UserStoreModel> {
} }
} }
const userStore: UserStore = UserStore.getInstance(); export const userStore: UserStore = UserStore.getInstance();
export { userStore }

View File

@ -1,4 +1,4 @@
import { computed, toJS } from "mobx"; import { action, computed, toJS } from "mobx";
import { BaseStore } from "./base-store"; import { BaseStore } from "./base-store";
import { clusterStore } from "./cluster-store" import { clusterStore } from "./cluster-store"
@ -42,6 +42,7 @@ export class WorkspaceStore extends BaseStore<WorkspaceStoreModel> {
return this.workspaces.findIndex(workspace => workspace.id === id); return this.workspaces.findIndex(workspace => workspace.id === id);
} }
@action
public saveWorkspace(newWorkspace: Workspace) { public saveWorkspace(newWorkspace: Workspace) {
const workspace = this.getById(newWorkspace.id); const workspace = this.getById(newWorkspace.id);
if (workspace) { if (workspace) {
@ -51,6 +52,7 @@ export class WorkspaceStore extends BaseStore<WorkspaceStoreModel> {
} }
} }
@action
public removeWorkspace(workspaceOrId: Workspace | WorkspaceId) { public removeWorkspace(workspaceOrId: Workspace | WorkspaceId) {
const workspace = this.getById(typeof workspaceOrId == "string" ? workspaceOrId : workspaceOrId.id); const workspace = this.getById(typeof workspaceOrId == "string" ? workspaceOrId : workspaceOrId.id);
if (!workspace) return; if (!workspace) return;
@ -60,7 +62,7 @@ export class WorkspaceStore extends BaseStore<WorkspaceStoreModel> {
const index = this.getIndexById(workspace.id); const index = this.getIndexById(workspace.id);
if (index > -1) { if (index > -1) {
this.data.workspaces.splice(index, 1) this.data.workspaces.splice(index, 1)
clusterStore.removeClustersByWorkspace(workspace.id) clusterStore.removeAllByWorkspaceId(workspace.id)
} }
} }
} }

View File

@ -1,8 +1,8 @@
import { KubeConfig } from "@kubernetes/client-node" import { KubeConfig } from "@kubernetes/client-node"
import { PromiseIpc } from "electron-promise-ipc" import { PromiseIpc } from "electron-promise-ipc"
import http from "http" import http from "http"
import { Cluster, ClusterBaseInfo } from "./cluster" import { Cluster } from "./cluster"
import { clusterStore } from "../common/cluster-store" import { ClusterModel, clusterStore } from "../common/cluster-store"
import * as k8s from "./k8s" import * as k8s from "./k8s"
import logger from "./logger" import logger from "./logger"
import { LensProxy } from "./proxy" import { LensProxy } from "./proxy"
@ -14,6 +14,8 @@ import filenamify from "filenamify"
import { v4 as uuid } from "uuid" import { v4 as uuid } from "uuid"
import { apiPrefix } from "../common/vars"; import { apiPrefix } from "../common/vars";
// todo: refactor + reuse parts of cluster-store more heavily
export type FeatureInstallRequest = { export type FeatureInstallRequest = {
name: string; name: string;
clusterId: string; clusterId: string;
@ -32,7 +34,10 @@ export type ClusterIconUpload = {
} }
export class ClusterManager { export class ClusterManager {
public static readonly clusterIconDir = path.join(app.getPath("userData"), "icons") static get clusterIconDir(){
return path.join(app.getPath("userData"), "icons")
}
protected promiseIpc: any protected promiseIpc: any
protected proxyServer: LensProxy protected proxyServer: LensProxy
protected port: number protected port: number
@ -83,7 +88,7 @@ export class ClusterManager {
return kc; return kc;
} }
protected async addNewCluster(clusterData: ClusterBaseInfo): Promise<Cluster> { protected async addNewCluster(clusterData: ClusterModel): Promise<Cluster> {
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
try { try {
const kc = this.loadKubeConfig(clusterData.kubeConfigPath) const kc = this.loadKubeConfig(clusterData.kubeConfigPath)
@ -110,7 +115,7 @@ export class ClusterManager {
} }
protected listenEvents() { protected listenEvents() {
this.promiseIpc.on("addCluster", async (clusterData: ClusterBaseInfo) => { this.promiseIpc.on("addCluster", async (clusterData: ClusterModel) => {
logger.debug(`IPC: addCluster`) logger.debug(`IPC: addCluster`)
const cluster = await this.addNewCluster(clusterData) const cluster = await this.addNewCluster(clusterData)
return { return {
@ -174,10 +179,10 @@ export class ClusterManager {
} }
try { try {
const clusterIcon = await this.uploadClusterIcon(cluster, fileUpload.name, fileUpload.path) const clusterIcon = await this.uploadClusterIcon(cluster, fileUpload.name, fileUpload.path)
clusterStore.reloadCluster(cluster); // clusterStore.reloadCluster(cluster);
if(!cluster.preferences) cluster.preferences = {}; if(!cluster.preferences) cluster.preferences = {};
cluster.preferences.icon = clusterIcon cluster.preferences.icon = clusterIcon
clusterStore.saveCluster(cluster); // clusterStore.saveCluster(cluster);
return {success: true, cluster: cluster.toClusterInfo(), message: ""} return {success: true, cluster: cluster.toClusterInfo(), message: ""}
} catch(error) { } catch(error) {
return {success: false, message: error} return {success: false, message: error}
@ -189,7 +194,7 @@ export class ClusterManager {
const cluster = this.getCluster(id) const cluster = this.getCluster(id)
if (cluster && cluster.preferences) { if (cluster && cluster.preferences) {
cluster.preferences.icon = null; cluster.preferences.icon = null;
clusterStore.saveCluster(cluster) // clusterStore.saveCluster(cluster)
return {success: true, cluster: cluster.toClusterInfo(), message: ""} return {success: true, cluster: cluster.toClusterInfo(), message: ""}
} else { } else {
return {success: false, message: "Cluster not found"} return {success: false, message: "Cluster not found"}
@ -221,7 +226,7 @@ export class ClusterManager {
logger.debug(`IPC: clusterStored: ${clusterId}`) logger.debug(`IPC: clusterStored: ${clusterId}`)
const cluster = this.clusters.get(clusterId) const cluster = this.clusters.get(clusterId)
if (cluster) { if (cluster) {
clusterStore.reloadCluster(cluster); // clusterStore.reloadCluster(cluster);
cluster.stopServer() cluster.stopServer()
} }
}); });
@ -244,7 +249,7 @@ export class ClusterManager {
const cluster = this.clusters.get(id) const cluster = this.clusters.get(id)
if (cluster) { if (cluster) {
cluster.stopServer() cluster.stopServer()
clusterStore.removeCluster(cluster.id); clusterStore.removeById(cluster.id);
this.clusters.delete(cluster.id) this.clusters.delete(cluster.id)
} }
return Array.from(this.clusters.values()) return Array.from(this.clusters.values())

View File

@ -1,7 +1,7 @@
import { ContextHandler } from "./context-handler" import { ContextHandler } from "./context-handler"
import { FeatureStatusMap } from "./feature" import { FeatureStatusMap } from "./feature"
import * as k8s from "./k8s" import * as k8s from "./k8s"
import { clusterStore } from "../common/cluster-store" import { ClusterId, ClusterModel, ClusterPreferences } from "../common/cluster-store"
import logger from "./logger" import logger from "./logger"
import { AuthorizationV1Api, CoreV1Api, KubeConfig, V1ResourceAttributes } from "@kubernetes/client-node" import { AuthorizationV1Api, CoreV1Api, KubeConfig, V1ResourceAttributes } from "@kubernetes/client-node"
import * as fm from "./feature-manager"; import * as fm from "./feature-manager";
@ -10,6 +10,7 @@ import { KubeconfigManager } from "./kubeconfig-manager"
import { PromiseIpc } from "electron-promise-ipc" import { PromiseIpc } from "electron-promise-ipc"
import request from "request-promise-native" import request from "request-promise-native"
import { apiPrefix } from "../common/vars"; import { apiPrefix } from "../common/vars";
import type { ClusterInfo } from "../renderer/_vue/store/modules/clusters";
enum ClusterStatus { enum ClusterStatus {
AccessGranted = 2, AccessGranted = 2,
@ -17,49 +18,8 @@ enum ClusterStatus {
Offline = 0 Offline = 0
} }
export interface ClusterBaseInfo { export class Cluster implements ClusterModel {
id: string; public id: ClusterId;
kubeConfig?: string;
kubeConfigPath: string;
contextName: string;
preferences?: ClusterPreferences;
port?: number;
workspace?: string;
}
export interface ClusterInfo extends ClusterBaseInfo {
url: string;
apiUrl: string;
online?: boolean;
accessible?: boolean;
failureReason?: string;
nodes?: number;
version?: string;
distribution?: string;
isAdmin?: boolean;
features?: FeatureStatusMap;
kubeCtl?: Kubectl;
contextName: string;
}
export type ClusterPreferences = {
terminalCWD?: string;
clusterName?: string;
prometheus?: {
namespace: string;
service: string;
port: number;
prefix: string;
};
prometheusProvider?: {
type: string;
};
icon?: string;
httpsProxy?: string;
}
export class Cluster implements ClusterInfo {
public id: string;
public workspace: string; public workspace: string;
public contextHandler: ContextHandler; public contextHandler: ContextHandler;
public contextName: string; public contextName: string;
@ -84,8 +44,8 @@ export class Cluster implements ClusterInfo {
protected kubeconfigManager: KubeconfigManager; protected kubeconfigManager: KubeconfigManager;
constructor(clusterInfo: ClusterBaseInfo) { constructor(jsonModel: ClusterModel) {
if (clusterInfo) Object.assign(this, clusterInfo) if (jsonModel) Object.assign(this, jsonModel)
if (!this.preferences) this.preferences = {} if (!this.preferences) this.preferences = {}
} }
@ -129,7 +89,7 @@ export class Cluster implements ClusterInfo {
} }
public async refreshCluster() { public async refreshCluster() {
clusterStore.reloadCluster(this) // clusterStore.reloadCluster(this)
this.contextHandler.setClusterPreferences(this.preferences) this.contextHandler.setClusterPreferences(this.preferences)
const connectionStatus = await this.getConnectionStatus() const connectionStatus = await this.getConnectionStatus()
@ -155,7 +115,7 @@ export class Cluster implements ClusterInfo {
} }
public save() { public save() {
clusterStore.saveCluster(this) // clusterStore.saveCluster(this)
} }
public toClusterInfo(): ClusterInfo { public toClusterInfo(): ClusterInfo {

View File

@ -5,9 +5,10 @@ import * as url from "url"
import logger from "./logger" import logger from "./logger"
import { getFreePort } from "./port" import { getFreePort } from "./port"
import { KubeAuthProxy } from "./kube-auth-proxy" import { KubeAuthProxy } from "./kube-auth-proxy"
import { Cluster, ClusterPreferences } from "./cluster" import { Cluster } from "./cluster"
import { prometheusProviders } from "../common/prometheus-providers" import { prometheusProviders } from "../common/prometheus-providers"
import { PrometheusService, PrometheusProvider } from "./prometheus/provider-registry" import type { PrometheusService, PrometheusProvider } from "./prometheus/provider-registry"
import type { ClusterPreferences } from "../common/cluster-store";
export class ContextHandler { export class ContextHandler {
public contextName: string public contextName: string

View File

@ -1,15 +1,9 @@
// Main process // Main process
import "../common/system-ca" import "../common/system-ca"
import { app, dialog, protocol } from "electron"
import { isMac, vueAppName, isDevelopment } from "../common/vars";
if (isDevelopment) {
const appName = 'LensDev';
const appData = app.getPath('appData');
app.setName(appName);
app.setPath('userData', path.join(appData, appName));
}
import "../common/prometheus-providers" import "../common/prometheus-providers"
import { app, dialog, protocol } from "electron"
import { isDevelopment, isMac, vueAppName } from "../common/vars";
import { PromiseIpc } from "electron-promise-ipc" import { PromiseIpc } from "electron-promise-ipc"
import path from "path" import path from "path"
import { format as formatUrl } from "url" import { format as formatUrl } from "url"
@ -17,7 +11,6 @@ import logger from "./logger"
import initMenu from "./menu" import initMenu from "./menu"
import * as proxy from "./proxy" import * as proxy from "./proxy"
import { WindowManager } from "./window-manager"; import { WindowManager } from "./window-manager";
import { clusterStore } from "../common/cluster-store"
import { ClusterManager } from "./cluster-manager"; import { ClusterManager } from "./cluster-manager";
import AppUpdater from "./app-updater" import AppUpdater from "./app-updater"
import { shellSync } from "./shell-sync" import { shellSync } from "./shell-sync"
@ -25,8 +18,17 @@ import { getFreePort } from "./port"
import { mangleProxyEnv } from "./proxy-env" import { mangleProxyEnv } from "./proxy-env"
import { findMainWebContents } from "./webcontents" import { findMainWebContents } from "./webcontents"
import { registerStaticProtocol } from "../common/register-static"; import { registerStaticProtocol } from "../common/register-static";
import { clusterStore } from "../common/cluster-store"
import { userStore } from "../common/user-store";
import { tracker } from "../common/tracker"; import { tracker } from "../common/tracker";
if (isDevelopment) {
const appName = "LensDev";
const appData = app.getPath("appData");
app.setName(appName);
app.setPath("userData", path.join(appData, appName));
}
mangleProxyEnv() mangleProxyEnv()
if (app.commandLine.getSwitchValue("proxy-server") !== "") { if (app.commandLine.getSwitchValue("proxy-server") !== "") {
process.env.HTTPS_PROXY = app.commandLine.getSwitchValue("proxy-server") process.env.HTTPS_PROXY = app.commandLine.getSwitchValue("proxy-server")
@ -69,8 +71,14 @@ async function main() {
app.quit(); app.quit();
} }
// preload required stores
await Promise.all([
userStore.load(),
clusterStore.load(),
]);
// create cluster manager // create cluster manager
clusterManager = new ClusterManager(clusterStore.getAllClusterObjects(), port) clusterManager = new ClusterManager(clusterStore.clusters, port)
// run proxy // run proxy
try { try {
proxyServer = proxy.listen(port, clusterManager) proxyServer = proxy.listen(port, clusterManager)

View File

@ -46,14 +46,16 @@ if(isDevelopment) {
if(process.platform === "win32") bundledPath = `${bundledPath}.exe` if(process.platform === "win32") bundledPath = `${bundledPath}.exe`
export class Kubectl { export class Kubectl {
public kubectlVersion: string public kubectlVersion: string
protected directory: string protected directory: string
protected url: string protected url: string
protected path: string protected path: string
protected dirname: string protected dirname: string
public static readonly kubectlDir = path.join((app || remote.app).getPath("userData"), "binaries", "kubectl") static get kubectlDir(){
return path.join((app || remote.app).getPath("userData"), "binaries", "kubectl")
}
public static readonly bundledKubectlPath = bundledPath public static readonly bundledKubectlPath = bundledPath
public static readonly bundledKubectlVersion: string = bundledVersion public static readonly bundledKubectlVersion: string = bundledVersion
private static bundledInstance: Kubectl; private static bundledInstance: Kubectl;

View File

@ -1,21 +1,21 @@
// Move embedded kubeconfig into separate file and add reference to it to cluster settings // Move embedded kubeconfig into separate file and add reference to it to cluster settings
import path from "path" import path from "path"
import { app } from "electron" import { app, remote } from "electron"
import { migration } from "../migration-wrapper"; import { migration } from "../migration-wrapper";
import { ensureDirSync } from "fs-extra" import { ensureDirSync } from "fs-extra"
import { KubeConfig } from "@kubernetes/client-node"; import { KubeConfig } from "@kubernetes/client-node";
import { writeEmbeddedKubeConfig } from "../../common/utils/kubeconfig" import { writeEmbeddedKubeConfig } from "../../common/utils/kubeconfig"
import { ClusterBaseInfo } from "../../main/cluster"; import { ClusterModel } from "../../common/cluster-store";
export default migration({ export default migration({
version: "3.6.0-beta.1", version: "3.6.0-beta.1",
run(store, log: (...args: any[]) => void) { run(store, log: (...args: any[]) => void) {
const migratingClusters: ClusterBaseInfo[] = [] const migratingClusters: ClusterModel[] = []
const kubeConfigBase = path.join(app.getPath("userData"), "kubeconfigs") const kubeConfigBase = path.join((app || remote.app).getPath("userData"), "kubeconfigs")
ensureDirSync(kubeConfigBase) ensureDirSync(kubeConfigBase)
const storedClusters: ClusterBaseInfo[] = store.get("clusters") const storedClusters: ClusterModel[] = store.get("clusters")
if (!storedClusters) return if (!storedClusters) return
log("Number of clusters to migrate: ", storedClusters.length) log("Number of clusters to migrate: ", storedClusters.length)

View File

@ -22,7 +22,6 @@
import ClusterMenuItem from "@/_vue/components/MainMenu/ClusterMenuItem"; import ClusterMenuItem from "@/_vue/components/MainMenu/ClusterMenuItem";
import AddClusterMenuItem from "@/_vue/components/MainMenu/AddClusterMenuItem"; import AddClusterMenuItem from "@/_vue/components/MainMenu/AddClusterMenuItem";
import draggable from 'vuedraggable' import draggable from 'vuedraggable'
import { clusterStore } from "../../../../common/cluster-store"
import { isMac } from "../../../../common/vars" import { isMac } from "../../../../common/vars"
const {remote} = require('electron') const {remote} = require('electron')
@ -47,7 +46,7 @@ export default {
}, },
set: function (clusters) { set: function (clusters) {
this.$store.commit("updateClusters", clusters); this.$store.commit("updateClusters", clusters);
clusterStore.storeClusters(clusters); // clusterStore.storeClusters(clusters);
} }
} }
}, },

View File

@ -10,6 +10,8 @@ import App from './App'
import router from './router' import router from './router'
import store from './store' import store from './store'
import { userStore } from "../../common/user-store" import { userStore } from "../../common/user-store"
import { workspaceStore } from "../../common/workspace-store"
import { clusterStore } from "../../common/cluster-store"
const promiseIpc = new PromiseIpc({maxTimeoutMs: 6000}); const promiseIpc = new PromiseIpc({maxTimeoutMs: 6000});
@ -29,9 +31,13 @@ Vue.mixin({
// any initialization we want to do for app state // any initialization we want to do for app state
setTimeout(async () => { setTimeout(async () => {
await userStore.whenLoaded; await Promise.all([
await store.dispatch('init') userStore.whenLoaded,
workspaceStore.whenLoaded,
clusterStore.whenLoaded,
]);
await store.dispatch('init')
new Vue({ new Vue({
components: {App}, components: {App},
store, store,

View File

@ -1,12 +1,29 @@
import Vue from "vue" import Vue from "vue"
import { ClusterInfo } from "../../../../main/cluster"
import { ActionTree, GetterTree, MutationTree } from "vuex" import { ActionTree, GetterTree, MutationTree } from "vuex"
import { PromiseIpc } from 'electron-promise-ipc' import { PromiseIpc } from 'electron-promise-ipc'
import { clusterStore } from "../../../../common/cluster-store" import { ClusterModel } from "../../../../common/cluster-store"
import { Workspace } from "../../../../common/workspace-store" import { Workspace } from "../../../../common/workspace-store"
import { tracker } from "../../../../common/tracker"; import { tracker } from "../../../../common/tracker";
import { FeatureStatusMap } from "../../../../main/feature";
import { Kubectl } from "../../../../main/kubectl";
const promiseIpc = new PromiseIpc({ maxTimeoutMs: 120000 }); /**
* @deprecated
*/
export interface ClusterInfo extends ClusterModel {
url: string;
apiUrl: string;
online?: boolean;
accessible?: boolean;
failureReason?: string;
nodes?: number;
version?: string;
distribution?: string;
isAdmin?: boolean;
features?: FeatureStatusMap;
kubeCtl?: Kubectl;
contextName: string;
}
export interface LensWebview { export interface LensWebview {
id: string; id: string;
@ -19,6 +36,8 @@ export interface ClusterState {
clusters: ClusterInfo[]; clusters: ClusterInfo[];
} }
const promiseIpc = new PromiseIpc({ maxTimeoutMs: 120000 });
const state: ClusterState = { const state: ClusterState = {
lenses: [], lenses: [],
clusters: [] clusters: []
@ -218,7 +237,7 @@ const actions: ActionTree<ClusterState, any> = {
}) })
}, },
storeCluster({ commit }, cluster: ClusterInfo) { storeCluster({ commit }, cluster: ClusterInfo) {
clusterStore.saveCluster(cluster); // clusterStore.saveCluster(cluster);
commit("updateCluster", cluster) commit("updateCluster", cluster)
promiseIpc.send("clusterStored", cluster.id) promiseIpc.send("clusterStored", cluster.id)
} }