mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Move ownership and enabling tracking into cluster store
Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
parent
b066fb3527
commit
29dadd478a
@ -28,11 +28,7 @@ class KubectlDownloader {
|
|||||||
resolveWithFullResponse: true
|
resolveWithFullResponse: true
|
||||||
}).catch((error) => { console.log(error); });
|
}).catch((error) => { console.log(error); });
|
||||||
|
|
||||||
if (response.headers["etag"]) {
|
return response.headers?.["etag"]?.replace(/"/g, "") ?? "";
|
||||||
return response.headers["etag"].replace(/"/g, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
return "";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async checkBinary() {
|
public async checkBinary() {
|
||||||
@ -87,7 +83,7 @@ class KubectlDownloader {
|
|||||||
throw(error);
|
throw(error);
|
||||||
});
|
});
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise<void>((resolve, reject) => {
|
||||||
file.on("close", () => {
|
file.on("close", () => {
|
||||||
console.log("kubectl binary download closed");
|
console.log("kubectl binary download closed");
|
||||||
fs.chmod(this.path, 0o755, (err) => {
|
fs.chmod(this.path, 0o755, (err) => {
|
||||||
@ -116,4 +112,3 @@ downloads.forEach((dlOpts) => {
|
|||||||
console.log(`Downloading: ${JSON.stringify(dlOpts)}`);
|
console.log(`Downloading: ${JSON.stringify(dlOpts)}`);
|
||||||
downloader.downloadKubectl().then(() => downloader.checkBinary().then(() => console.log("Download complete")));
|
downloader.downloadKubectl().then(() => downloader.checkBinary().then(() => console.log("Download complete")));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -362,12 +362,12 @@
|
|||||||
"terser-webpack-plugin": "^3.0.3",
|
"terser-webpack-plugin": "^3.0.3",
|
||||||
"ts-jest": "^26.1.0",
|
"ts-jest": "^26.1.0",
|
||||||
"ts-loader": "^7.0.5",
|
"ts-loader": "^7.0.5",
|
||||||
"ts-node": "^8.10.2",
|
"ts-node": "^9.1.1",
|
||||||
"type-fest": "^0.18.0",
|
"type-fest": "^0.18.0",
|
||||||
"typedoc": "0.17.0-3",
|
"typedoc": "0.17.0-3",
|
||||||
"typedoc-plugin-markdown": "^2.4.0",
|
"typedoc-plugin-markdown": "^2.4.0",
|
||||||
"typeface-roboto": "^0.0.75",
|
"typeface-roboto": "^0.0.75",
|
||||||
"typescript": "4.0.2",
|
"typescript": "^4.2.3",
|
||||||
"url-loader": "^4.1.0",
|
"url-loader": "^4.1.0",
|
||||||
"webpack": "^4.44.2",
|
"webpack": "^4.44.2",
|
||||||
"webpack-cli": "^3.3.11",
|
"webpack-cli": "^3.3.11",
|
||||||
|
|||||||
@ -88,7 +88,7 @@ describe("empty config", () => {
|
|||||||
expect(storedCluster.id).toBe("foo");
|
expect(storedCluster.id).toBe("foo");
|
||||||
expect(storedCluster.preferences.terminalCWD).toBe("/tmp");
|
expect(storedCluster.preferences.terminalCWD).toBe("/tmp");
|
||||||
expect(storedCluster.preferences.icon).toBe("data:image/jpeg;base64, iVBORw0KGgoAAAANSUhEUgAAA1wAAAKoCAYAAABjkf5");
|
expect(storedCluster.preferences.icon).toBe("data:image/jpeg;base64, iVBORw0KGgoAAAANSUhEUgAAA1wAAAKoCAYAAABjkf5");
|
||||||
expect(storedCluster.enabled).toBe(true);
|
expect(clusterStore.isClusterEnabled(storedCluster)).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("adds cluster to default workspace", () => {
|
it("adds cluster to default workspace", () => {
|
||||||
@ -265,8 +265,8 @@ describe("config with existing clusters", () => {
|
|||||||
it("marks owned cluster disabled by default", () => {
|
it("marks owned cluster disabled by default", () => {
|
||||||
const storedClusters = clusterStore.clustersList;
|
const storedClusters = clusterStore.clustersList;
|
||||||
|
|
||||||
expect(storedClusters[0].enabled).toBe(true);
|
expect(clusterStore.isClusterEnabled(storedClusters[0])).toBe(true);
|
||||||
expect(storedClusters[2].enabled).toBe(false);
|
expect(clusterStore.isClusterEnabled(storedClusters[2])).toBe(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -336,9 +336,9 @@ users:
|
|||||||
const storedClusters = clusterStore.clustersList;
|
const storedClusters = clusterStore.clustersList;
|
||||||
|
|
||||||
expect(storedClusters.length).toBe(2);
|
expect(storedClusters.length).toBe(2);
|
||||||
expect(storedClusters[0].enabled).toBeFalsy;
|
expect(clusterStore.isClusterEnabled(storedClusters[0])).toBeFalsy;
|
||||||
expect(storedClusters[1].id).toBe("cluster2");
|
expect(storedClusters[1].id).toBe("cluster2");
|
||||||
expect(storedClusters[1].enabled).toBeTruthy;
|
expect(clusterStore.isClusterEnabled(storedClusters[1])).toBeTruthy;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -11,11 +11,12 @@ import { appEventBus } from "./event-bus";
|
|||||||
import { dumpConfigYaml } from "./kube-helpers";
|
import { dumpConfigYaml } from "./kube-helpers";
|
||||||
import { saveToAppFiles } from "./utils/saveToAppFiles";
|
import { saveToAppFiles } from "./utils/saveToAppFiles";
|
||||||
import { KubeConfig } from "@kubernetes/client-node";
|
import { KubeConfig } from "@kubernetes/client-node";
|
||||||
import { handleRequest, requestMain, subscribeToBroadcast, unsubscribeAllFromBroadcast } from "./ipc";
|
import { broadcastMessage, handleRequest, InvalidKubeconfigChannel, requestMain, subscribeToBroadcast, unsubscribeAllFromBroadcast } from "./ipc";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import move from "array-move";
|
import move from "array-move";
|
||||||
import type { WorkspaceId } from "./workspace-store";
|
import type { WorkspaceId } from "./workspace-store";
|
||||||
import { ResourceType } from "../renderer/components/+cluster-settings/components/cluster-metrics-setting";
|
import { ResourceType } from "../renderer/components/+cluster-settings/components/cluster-metrics-setting";
|
||||||
|
import { LensExtensionId } from "../extensions/lens-extension";
|
||||||
|
|
||||||
export interface ClusterIconUpload {
|
export interface ClusterIconUpload {
|
||||||
clusterId: string;
|
clusterId: string;
|
||||||
@ -34,8 +35,9 @@ export type ClusterPrometheusMetadata = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export interface ClusterStoreModel {
|
export interface ClusterStoreModel {
|
||||||
activeCluster?: ClusterId; // last opened cluster
|
activeClusterId?: ClusterId; // last opened cluster
|
||||||
clusters?: ClusterModel[];
|
clusters?: ClusterModel[];
|
||||||
|
clusterOwners?: [ClusterId, LensExtensionId][];
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ClusterId = string;
|
export type ClusterId = string;
|
||||||
@ -59,11 +61,6 @@ export interface ClusterModel {
|
|||||||
/** Metadata */
|
/** Metadata */
|
||||||
metadata?: ClusterMetadata;
|
metadata?: ClusterMetadata;
|
||||||
|
|
||||||
/**
|
|
||||||
* If extension sets ownerRef it has to explicitly mark a cluster as enabled during onActive (or when cluster is saved)
|
|
||||||
*/
|
|
||||||
ownerRef?: string;
|
|
||||||
|
|
||||||
/** List of accessible namespaces */
|
/** List of accessible namespaces */
|
||||||
accessibleNamespaces?: string[];
|
accessibleNamespaces?: string[];
|
||||||
|
|
||||||
@ -71,6 +68,16 @@ export interface ClusterModel {
|
|||||||
kubeConfig?: string; // yaml
|
kubeConfig?: string; // yaml
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ClusterManagementRecord {
|
||||||
|
ownerId: LensExtensionId;
|
||||||
|
enabled: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GetByWorkspaceIdOptions {
|
||||||
|
includeDisabled?: boolean; // default false
|
||||||
|
sortByIconOrder?: boolean; // default true
|
||||||
|
}
|
||||||
|
|
||||||
export interface ClusterPreferences extends ClusterPrometheusPreferences {
|
export interface ClusterPreferences extends ClusterPrometheusPreferences {
|
||||||
terminalCWD?: string;
|
terminalCWD?: string;
|
||||||
clusterName?: string;
|
clusterName?: string;
|
||||||
@ -92,6 +99,22 @@ export interface ClusterPrometheusPreferences {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function splitAddClusterArgs(args: ClusterModel[] | [...ClusterModel[], string]): [ClusterModel[]] | [ClusterModel[], string] {
|
||||||
|
const lastArg = args.pop();
|
||||||
|
|
||||||
|
if (lastArg) {
|
||||||
|
return [[]];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof lastArg === "string") {
|
||||||
|
return [args as ClusterModel[], lastArg];
|
||||||
|
}
|
||||||
|
|
||||||
|
args.push(lastArg);
|
||||||
|
|
||||||
|
return [args as ClusterModel[]];
|
||||||
|
}
|
||||||
|
|
||||||
export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
||||||
static getCustomKubeConfigPath(clusterId: ClusterId): string {
|
static getCustomKubeConfigPath(clusterId: ClusterId): string {
|
||||||
return path.resolve((app || remote.app).getPath("userData"), "kubeconfigs", clusterId);
|
return path.resolve((app || remote.app).getPath("userData"), "kubeconfigs", clusterId);
|
||||||
@ -106,9 +129,11 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
|||||||
return filePath;
|
return filePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
@observable activeCluster: ClusterId;
|
@observable activeClusterId: ClusterId;
|
||||||
@observable removedClusters = observable.map<ClusterId, Cluster>();
|
@observable removedClusters = observable.map<ClusterId, Cluster>();
|
||||||
@observable clusters = observable.map<ClusterId, Cluster>();
|
@observable clusters = observable.map<ClusterId, Cluster>();
|
||||||
|
@observable clusterManagingInfo = observable.map<ClusterId, ClusterManagementRecord>();
|
||||||
|
@observable erroredClusterModels = observable.array<ClusterModel>();
|
||||||
|
|
||||||
private static stateRequestChannel = "cluster:states";
|
private static stateRequestChannel = "cluster:states";
|
||||||
|
|
||||||
@ -189,28 +214,50 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
get activeClusterId() {
|
|
||||||
return this.activeCluster;
|
|
||||||
}
|
|
||||||
|
|
||||||
@computed get clustersList(): Cluster[] {
|
@computed get clustersList(): Cluster[] {
|
||||||
return Array.from(this.clusters.values());
|
return Array.from(this.clusters.values());
|
||||||
}
|
}
|
||||||
|
|
||||||
@computed get enabledClustersList(): Cluster[] {
|
@computed get enabledClustersList(): Cluster[] {
|
||||||
return this.clustersList.filter((c) => c.enabled);
|
return this.clustersList.filter(c => this.isClusterEnabled(c));
|
||||||
}
|
}
|
||||||
|
|
||||||
@computed get active(): Cluster | null {
|
@computed get active(): Cluster | null {
|
||||||
return this.getById(this.activeCluster);
|
return this.getById(this.activeClusterId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@computed get connectedClustersList(): Cluster[] {
|
@computed get connectedClustersList(): Cluster[] {
|
||||||
return this.clustersList.filter((c) => !c.disconnected);
|
return this.clustersList.filter((c) => !c.disconnected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param clusterOrId The cluster or its ID to check if it is owned
|
||||||
|
* @returns true if an extension has claimed to be managing a cluster
|
||||||
|
*/
|
||||||
|
isClusterManaged(clusterOrId: Cluster | ClusterId): boolean {
|
||||||
|
const clusterId = typeof clusterOrId === "string"
|
||||||
|
? clusterOrId
|
||||||
|
: clusterOrId.id;
|
||||||
|
|
||||||
|
return this.clusterManagingInfo.has(clusterId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the enabled status of a cluster
|
||||||
|
* @param clusterOrId The cluster or its ID to check if it is owned
|
||||||
|
* @returns true if not managed, else true if owner has marked as enabled
|
||||||
|
*/
|
||||||
|
isClusterEnabled(clusterOrId: Cluster | ClusterId): boolean {
|
||||||
|
const clusterId = typeof clusterOrId === "string"
|
||||||
|
? clusterOrId
|
||||||
|
: clusterOrId.id;
|
||||||
|
|
||||||
|
return this.clusterManagingInfo.get(clusterId)?.enabled ?? true;
|
||||||
|
}
|
||||||
|
|
||||||
isActive(id: ClusterId) {
|
isActive(id: ClusterId) {
|
||||||
return this.activeCluster === id;
|
return this.activeClusterId === id;
|
||||||
}
|
}
|
||||||
|
|
||||||
isMetricHidden(resource: ResourceType) {
|
isMetricHidden(resource: ResourceType) {
|
||||||
@ -221,7 +268,7 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
|||||||
setActive(id: ClusterId) {
|
setActive(id: ClusterId) {
|
||||||
const clusterId = this.clusters.has(id) ? id : null;
|
const clusterId = this.clusters.has(id) ? id : null;
|
||||||
|
|
||||||
this.activeCluster = clusterId;
|
this.activeClusterId = clusterId;
|
||||||
workspaceStore.setLastActiveClusterId(clusterId);
|
workspaceStore.setLastActiveClusterId(clusterId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -249,37 +296,50 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
|||||||
return this.clusters.get(id);
|
return this.clusters.get(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
getByWorkspaceId(workspaceId: string): Cluster[] {
|
getByWorkspaceId(workspaceId: string, options?: GetByWorkspaceIdOptions): Cluster[] {
|
||||||
|
const includeDisabled = options?.includeDisabled ?? false;
|
||||||
|
const sortByIconOrder = options?.sortByIconOrder ?? true;
|
||||||
|
|
||||||
const clusters = Array.from(this.clusters.values())
|
const clusters = Array.from(this.clusters.values())
|
||||||
.filter(cluster => cluster.workspace === workspaceId);
|
.filter(cluster => (
|
||||||
|
cluster.workspace === workspaceId
|
||||||
|
&& (
|
||||||
|
includeDisabled
|
||||||
|
|| this.isClusterEnabled(cluster)
|
||||||
|
)
|
||||||
|
));
|
||||||
|
|
||||||
return _.sortBy(clusters, cluster => cluster.preferences.iconOrder);
|
if (sortByIconOrder) {
|
||||||
}
|
return _.sortBy(clusters, cluster => cluster.preferences.iconOrder);
|
||||||
|
}
|
||||||
@action
|
|
||||||
addClusters(...models: ClusterModel[]): Cluster[] {
|
|
||||||
const clusters: Cluster[] = [];
|
|
||||||
|
|
||||||
models.forEach(model => {
|
|
||||||
clusters.push(this.addCluster(model));
|
|
||||||
});
|
|
||||||
|
|
||||||
return clusters;
|
return clusters;
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
addCluster(model: ClusterModel | Cluster): Cluster {
|
addClusters<M extends ClusterModel, MM extends M[]>(...args: ([...MM] | [...MM, LensExtensionId])): Cluster[] {
|
||||||
|
const [models, ownerId] = splitAddClusterArgs(args);
|
||||||
|
|
||||||
|
return models.map(model => this.addCluster(model, ownerId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
addCluster(clusterOrModel: ClusterModel | Cluster, ownerId?: LensExtensionId): Cluster {
|
||||||
appEventBus.emit({ name: "cluster", action: "add" });
|
appEventBus.emit({ name: "cluster", action: "add" });
|
||||||
let cluster = model as Cluster;
|
|
||||||
|
|
||||||
if (!(model instanceof Cluster)) {
|
const cluster = clusterOrModel instanceof Cluster
|
||||||
cluster = new Cluster(model);
|
? clusterOrModel
|
||||||
|
: new Cluster(clusterOrModel);
|
||||||
|
|
||||||
|
if (ownerId) {
|
||||||
|
if (this.isClusterManaged(cluster)) {
|
||||||
|
throw new Error("Extension tried to claim an already managed cluster");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.clusterManagingInfo.set(cluster.id, { ownerId, enabled: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!cluster.isManaged) {
|
this.clusters.set(clusterOrModel.id, cluster);
|
||||||
cluster.enabled = true;
|
|
||||||
}
|
|
||||||
this.clusters.set(model.id, cluster);
|
|
||||||
|
|
||||||
return cluster;
|
return cluster;
|
||||||
}
|
}
|
||||||
@ -296,7 +356,7 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
|||||||
if (cluster) {
|
if (cluster) {
|
||||||
this.clusters.delete(clusterId);
|
this.clusters.delete(clusterId);
|
||||||
|
|
||||||
if (this.activeCluster === clusterId) {
|
if (this.activeClusterId === clusterId) {
|
||||||
this.setActive(null);
|
this.setActive(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -309,31 +369,43 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
|||||||
|
|
||||||
@action
|
@action
|
||||||
removeByWorkspaceId(workspaceId: string) {
|
removeByWorkspaceId(workspaceId: string) {
|
||||||
this.getByWorkspaceId(workspaceId).forEach(cluster => {
|
const workspaceClusters = this.getByWorkspaceId(workspaceId, {
|
||||||
this.removeById(cluster.id);
|
includeDisabled: true,
|
||||||
|
sortByIconOrder: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
for (const cluster of workspaceClusters) {
|
||||||
|
this.removeById(cluster.id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
protected fromStore({ activeCluster, clusters = [] }: ClusterStoreModel = {}) {
|
protected fromStore({ activeClusterId, clusters = [], clusterOwners = [] }: ClusterStoreModel = {}) {
|
||||||
const currentClusters = this.clusters.toJS();
|
const currentClusters = this.clusters.toJS();
|
||||||
const newClusters = new Map<ClusterId, Cluster>();
|
const newClusters = new Map<ClusterId, Cluster>();
|
||||||
const removedClusters = new Map<ClusterId, Cluster>();
|
const removedClusters = new Map<ClusterId, Cluster>();
|
||||||
|
const erroredClusterModels: ClusterModel[] = [];
|
||||||
|
const clusterOwnersMap = new Map<ClusterId, LensExtensionId>(clusterOwners);
|
||||||
|
|
||||||
// update new clusters
|
// update new clusters
|
||||||
for (const clusterModel of clusters) {
|
for (const clusterModel of clusters) {
|
||||||
let cluster = currentClusters.get(clusterModel.id);
|
if (currentClusters.has(clusterModel.id)) {
|
||||||
|
const cluster = currentClusters.get(clusterModel.id);
|
||||||
|
|
||||||
if (cluster) {
|
|
||||||
cluster.updateModel(clusterModel);
|
cluster.updateModel(clusterModel);
|
||||||
|
newClusters.set(clusterModel.id, cluster);
|
||||||
} else {
|
} else {
|
||||||
cluster = new Cluster(clusterModel);
|
try {
|
||||||
|
newClusters.set(clusterModel.id, new Cluster(clusterModel));
|
||||||
|
} catch (error) {
|
||||||
|
const { preferences, contextName: context, kubeConfigPath: kubeconfig } = clusterModel;
|
||||||
|
const clusterName = preferences?.clusterName || context;
|
||||||
|
|
||||||
if (!cluster.isManaged && cluster.apiUrl) {
|
logger.error(`[CLUSTER-STORE]: Failed to load kubeconfig for the cluster '${clusterName}'.`, { error, context, kubeconfig });
|
||||||
cluster.enabled = true;
|
broadcastMessage(InvalidKubeconfigChannel, clusterModel.id);
|
||||||
|
erroredClusterModels.push(clusterModel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
newClusters.set(clusterModel.id, cluster);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// update removed clusters
|
// update removed clusters
|
||||||
@ -343,15 +415,17 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.activeCluster = newClusters.get(activeCluster)?.enabled ? activeCluster : null;
|
this.activeClusterId = clusterOwnersMap.get(activeClusterId) ? null : activeClusterId;
|
||||||
this.clusters.replace(newClusters);
|
this.clusters.replace(newClusters);
|
||||||
this.removedClusters.replace(removedClusters);
|
this.removedClusters.replace(removedClusters);
|
||||||
|
this.erroredClusterModels.replace(erroredClusterModels);
|
||||||
|
this.clusterManagingInfo.replace(clusterOwnersMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
toJSON(): ClusterStoreModel {
|
toJSON(): ClusterStoreModel {
|
||||||
return toJS({
|
return toJS({
|
||||||
activeCluster: this.activeCluster,
|
activeClusterId: this.activeClusterId,
|
||||||
clusters: this.clustersList.map(cluster => cluster.toJSON()),
|
clusters: this.clustersList.map(cluster => cluster.toJSON()).concat(this.erroredClusterModels),
|
||||||
}, {
|
}, {
|
||||||
recurseEverything: true
|
recurseEverything: true
|
||||||
});
|
});
|
||||||
|
|||||||
0
src/common/utils/split-args.ts
Normal file
0
src/common/utils/split-args.ts
Normal file
@ -44,7 +44,7 @@ export abstract class ClusterFeature {
|
|||||||
*
|
*
|
||||||
* @param cluster the cluster that the feature is to be installed on
|
* @param cluster the cluster that the feature is to be installed on
|
||||||
*/
|
*/
|
||||||
abstract async install(cluster: Cluster): Promise<void>;
|
abstract install(cluster: Cluster): Promise<void>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* to be implemented in the derived class, this method is typically called by Lens when a user has indicated that this feature is to be upgraded. The implementation
|
* to be implemented in the derived class, this method is typically called by Lens when a user has indicated that this feature is to be upgraded. The implementation
|
||||||
@ -52,7 +52,7 @@ export abstract class ClusterFeature {
|
|||||||
*
|
*
|
||||||
* @param cluster the cluster that the feature is to be upgraded on
|
* @param cluster the cluster that the feature is to be upgraded on
|
||||||
*/
|
*/
|
||||||
abstract async upgrade(cluster: Cluster): Promise<void>;
|
abstract upgrade(cluster: Cluster): Promise<void>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* to be implemented in the derived class, this method is typically called by Lens when a user has indicated that this feature is to be uninstalled. The implementation
|
* to be implemented in the derived class, this method is typically called by Lens when a user has indicated that this feature is to be uninstalled. The implementation
|
||||||
@ -60,7 +60,7 @@ export abstract class ClusterFeature {
|
|||||||
*
|
*
|
||||||
* @param cluster the cluster that the feature is to be uninstalled from
|
* @param cluster the cluster that the feature is to be uninstalled from
|
||||||
*/
|
*/
|
||||||
abstract async uninstall(cluster: Cluster): Promise<void>;
|
abstract uninstall(cluster: Cluster): Promise<void>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* to be implemented in the derived class, this method is called periodically by Lens to determine details about the feature's current status. The implementation
|
* to be implemented in the derived class, this method is called periodically by Lens to determine details about the feature's current status. The implementation
|
||||||
@ -72,7 +72,7 @@ export abstract class ClusterFeature {
|
|||||||
*
|
*
|
||||||
* @return a promise, resolved with the updated ClusterFeatureStatus
|
* @return a promise, resolved with the updated ClusterFeatureStatus
|
||||||
*/
|
*/
|
||||||
abstract async updateStatus(cluster: Cluster): Promise<ClusterFeatureStatus>;
|
abstract updateStatus(cluster: Cluster): Promise<ClusterFeatureStatus>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* this is a helper method that conveniently applies kubernetes resources to the cluster.
|
* this is a helper method that conveniently applies kubernetes resources to the cluster.
|
||||||
|
|||||||
@ -18,14 +18,14 @@ export class ClusterStore extends Singleton {
|
|||||||
* Active cluster id
|
* Active cluster id
|
||||||
*/
|
*/
|
||||||
get activeClusterId(): string {
|
get activeClusterId(): string {
|
||||||
return internalClusterStore.activeCluster;
|
return internalClusterStore.activeClusterId;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set active cluster id
|
* Set active cluster id
|
||||||
*/
|
*/
|
||||||
set activeClusterId(id : ClusterId) {
|
set activeClusterId(id : ClusterId) {
|
||||||
internalClusterStore.activeCluster = id;
|
internalClusterStore.activeClusterId = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import type { IMetricsReqParams } from "../renderer/api/endpoints/metrics.api";
|
|||||||
import type { WorkspaceId } from "../common/workspace-store";
|
import type { WorkspaceId } from "../common/workspace-store";
|
||||||
import { action, comparer, computed, observable, reaction, toJS, when } from "mobx";
|
import { action, comparer, computed, observable, reaction, toJS, when } from "mobx";
|
||||||
import { apiKubePrefix } from "../common/vars";
|
import { apiKubePrefix } from "../common/vars";
|
||||||
import { broadcastMessage, InvalidKubeconfigChannel } from "../common/ipc";
|
import { broadcastMessage } from "../common/ipc";
|
||||||
import { ContextHandler } from "./context-handler";
|
import { ContextHandler } from "./context-handler";
|
||||||
import { AuthorizationV1Api, CoreV1Api, KubeConfig, V1ResourceAttributes } from "@kubernetes/client-node";
|
import { AuthorizationV1Api, CoreV1Api, KubeConfig, V1ResourceAttributes } from "@kubernetes/client-node";
|
||||||
import { Kubectl } from "./kubectl";
|
import { Kubectl } from "./kubectl";
|
||||||
@ -38,7 +38,6 @@ export type ClusterRefreshOptions = {
|
|||||||
|
|
||||||
export interface ClusterState {
|
export interface ClusterState {
|
||||||
initialized: boolean;
|
initialized: boolean;
|
||||||
enabled: boolean;
|
|
||||||
apiUrl: string;
|
apiUrl: string;
|
||||||
online: boolean;
|
online: boolean;
|
||||||
disconnected: boolean;
|
disconnected: boolean;
|
||||||
@ -71,12 +70,6 @@ export class Cluster implements ClusterModel, ClusterState {
|
|||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
public contextHandler: ContextHandler;
|
public contextHandler: ContextHandler;
|
||||||
/**
|
|
||||||
* Owner reference
|
|
||||||
*
|
|
||||||
* If extension sets this it needs to also mark cluster as enabled on activate (or when added to a store)
|
|
||||||
*/
|
|
||||||
public ownerRef: string;
|
|
||||||
protected kubeconfigManager: KubeconfigManager;
|
protected kubeconfigManager: KubeconfigManager;
|
||||||
protected eventDisposers: Function[] = [];
|
protected eventDisposers: Function[] = [];
|
||||||
protected activated = false;
|
protected activated = false;
|
||||||
@ -86,7 +79,7 @@ export class Cluster implements ClusterModel, ClusterState {
|
|||||||
whenReady = when(() => this.ready);
|
whenReady = when(() => this.ready);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Is cluster object initializinng on-going
|
* Is cluster object initializing on-going
|
||||||
*
|
*
|
||||||
* @observable
|
* @observable
|
||||||
*/
|
*/
|
||||||
@ -129,12 +122,6 @@ export class Cluster implements ClusterModel, ClusterState {
|
|||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
@observable kubeProxyUrl: string; // lens-proxy to kube-api url
|
@observable kubeProxyUrl: string; // lens-proxy to kube-api url
|
||||||
/**
|
|
||||||
* Is cluster instance enabled (disabled clusters are currently hidden)
|
|
||||||
*
|
|
||||||
* @observable
|
|
||||||
*/
|
|
||||||
@observable enabled = false; // only enabled clusters are visible to users
|
|
||||||
/**
|
/**
|
||||||
* Is cluster online
|
* Is cluster online
|
||||||
*
|
*
|
||||||
@ -258,23 +245,20 @@ export class Cluster implements ClusterModel, ClusterState {
|
|||||||
constructor(model: ClusterModel) {
|
constructor(model: ClusterModel) {
|
||||||
this.updateModel(model);
|
this.updateModel(model);
|
||||||
|
|
||||||
try {
|
const kubeconfig = this.getKubeconfig();
|
||||||
const kubeconfig = this.getKubeconfig();
|
|
||||||
|
|
||||||
validateKubeConfig(kubeconfig, this.contextName, { validateCluster: true, validateUser: false, validateExec: false});
|
validateKubeConfig(kubeconfig, this.contextName, { validateCluster: true, validateUser: false, validateExec: false});
|
||||||
this.apiUrl = kubeconfig.getCluster(kubeconfig.getContextObject(this.contextName).cluster).server;
|
this.apiUrl = kubeconfig.getCluster(kubeconfig.getContextObject(this.contextName).cluster).server;
|
||||||
} catch(err) {
|
|
||||||
logger.error(err);
|
|
||||||
logger.error(`[CLUSTER] Failed to load kubeconfig for the cluster '${this.name || this.contextName}' (context: ${this.contextName}, kubeconfig: ${this.kubeConfigPath}).`);
|
|
||||||
broadcastMessage(InvalidKubeconfigChannel, model.id);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
kubeConfig?: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Is cluster managed by an extension
|
* Is cluster managed by an extension
|
||||||
|
*
|
||||||
|
* @deprecated use `clusterStore.isClusterManaged(clusterId)`
|
||||||
*/
|
*/
|
||||||
get isManaged(): boolean {
|
get isManaged(): boolean {
|
||||||
return !!this.ownerRef;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -612,7 +596,6 @@ export class Cluster implements ClusterModel, ClusterState {
|
|||||||
workspace: this.workspace,
|
workspace: this.workspace,
|
||||||
preferences: this.preferences,
|
preferences: this.preferences,
|
||||||
metadata: this.metadata,
|
metadata: this.metadata,
|
||||||
ownerRef: this.ownerRef,
|
|
||||||
accessibleNamespaces: this.accessibleNamespaces,
|
accessibleNamespaces: this.accessibleNamespaces,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -627,7 +610,6 @@ export class Cluster implements ClusterModel, ClusterState {
|
|||||||
getState(): ClusterState {
|
getState(): ClusterState {
|
||||||
const state: ClusterState = {
|
const state: ClusterState = {
|
||||||
initialized: this.initialized,
|
initialized: this.initialized,
|
||||||
enabled: this.enabled,
|
|
||||||
apiUrl: this.apiUrl,
|
apiUrl: this.apiUrl,
|
||||||
online: this.online,
|
online: this.online,
|
||||||
ready: this.ready,
|
ready: this.ready,
|
||||||
|
|||||||
@ -273,7 +273,7 @@ export class Kubectl {
|
|||||||
|
|
||||||
logger.info(`Downloading kubectl ${this.kubectlVersion} from ${this.url} to ${this.path}`);
|
logger.info(`Downloading kubectl ${this.kubectlVersion} from ${this.url} to ${this.path}`);
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise<void>((resolve, reject) => {
|
||||||
const stream = customRequest({
|
const stream = customRequest({
|
||||||
url: this.url,
|
url: this.url,
|
||||||
gzip: true,
|
gzip: true,
|
||||||
|
|||||||
@ -183,7 +183,7 @@ export class LensBinary {
|
|||||||
throw(error);
|
throw(error);
|
||||||
});
|
});
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise<void>((resolve, reject) => {
|
||||||
file.on("close", () => {
|
file.on("close", () => {
|
||||||
this.logger.debug(`${this.originalBinaryName} binary download closed`);
|
this.logger.debug(`${this.originalBinaryName} binary download closed`);
|
||||||
if (!this.tarPath) fs.chmod(binaryPath, 0o755, (err) => {
|
if (!this.tarPath) fs.chmod(binaryPath, 0o755, (err) => {
|
||||||
|
|||||||
12
src/migrations/cluster-store/4.2.0.ts
Normal file
12
src/migrations/cluster-store/4.2.0.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { migration } from "../migration-wrapper";
|
||||||
|
|
||||||
|
export default migration({
|
||||||
|
version: "4.2.0",
|
||||||
|
run(store) {
|
||||||
|
const activeCluster = store.get("activeCluster");
|
||||||
|
|
||||||
|
if (activeCluster) {
|
||||||
|
store.set("activeClusterId", activeCluster);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -7,6 +7,7 @@ import version260Beta3 from "./2.6.0-beta.3";
|
|||||||
import version270Beta0 from "./2.7.0-beta.0";
|
import version270Beta0 from "./2.7.0-beta.0";
|
||||||
import version270Beta1 from "./2.7.0-beta.1";
|
import version270Beta1 from "./2.7.0-beta.1";
|
||||||
import version360Beta1 from "./3.6.0-beta.1";
|
import version360Beta1 from "./3.6.0-beta.1";
|
||||||
|
import version420 from "./4.2.0";
|
||||||
import snap from "./snap";
|
import snap from "./snap";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@ -17,5 +18,6 @@ export default {
|
|||||||
...version270Beta0,
|
...version270Beta0,
|
||||||
...version270Beta1,
|
...version270Beta1,
|
||||||
...version360Beta1,
|
...version360Beta1,
|
||||||
|
...version420,
|
||||||
...snap
|
...snap
|
||||||
};
|
};
|
||||||
|
|||||||
@ -56,7 +56,6 @@ export class WorkspaceClusterStore extends ItemStore<ClusterItem> {
|
|||||||
() => (
|
() => (
|
||||||
clusterStore
|
clusterStore
|
||||||
.getByWorkspaceId(this.workspaceId)
|
.getByWorkspaceId(this.workspaceId)
|
||||||
.filter(cluster => cluster.enabled)
|
|
||||||
.map(cluster => new ClusterItem(cluster))
|
.map(cluster => new ClusterItem(cluster))
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|||||||
@ -108,8 +108,8 @@ export class ClustersMenu extends React.Component<Props> {
|
|||||||
render() {
|
render() {
|
||||||
const { className } = this.props;
|
const { className } = this.props;
|
||||||
const workspace = workspaceStore.getById(workspaceStore.currentWorkspaceId);
|
const workspace = workspaceStore.getById(workspaceStore.currentWorkspaceId);
|
||||||
const clusters = clusterStore.getByWorkspaceId(workspace.id).filter(cluster => cluster.enabled);
|
const clusters = clusterStore.getByWorkspaceId(workspace.id);
|
||||||
const activeClusterId = clusterStore.activeCluster;
|
const activeClusterId = clusterStore.activeClusterId;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cssNames("ClustersMenu flex column", className)}>
|
<div className={cssNames("ClustersMenu flex column", className)}>
|
||||||
@ -192,7 +192,7 @@ export class ClustersMenu extends React.Component<Props> {
|
|||||||
@observer
|
@observer
|
||||||
export class ChooseCluster extends React.Component {
|
export class ChooseCluster extends React.Component {
|
||||||
@computed get options() {
|
@computed get options() {
|
||||||
const clusters = clusterStore.getByWorkspaceId(workspaceStore.currentWorkspaceId).filter(cluster => cluster.enabled);
|
const clusters = clusterStore.getByWorkspaceId(workspaceStore.currentWorkspaceId);
|
||||||
const options = clusters.map((cluster) => {
|
const options = clusters.map((cluster) => {
|
||||||
return { value: cluster.id, label: cluster.name };
|
return { value: cluster.id, label: cluster.name };
|
||||||
});
|
});
|
||||||
|
|||||||
@ -17,7 +17,7 @@ export class ReadableWebToNodeStream extends Readable {
|
|||||||
* Default web API stream reader
|
* Default web API stream reader
|
||||||
* https://developer.mozilla.org/en-US/docs/Web/API/ReadableStreamDefaultReader
|
* https://developer.mozilla.org/en-US/docs/Web/API/ReadableStreamDefaultReader
|
||||||
*/
|
*/
|
||||||
private reader: ReadableStreamReader;
|
private reader: ReadableStreamReader<any>;
|
||||||
private pendingRead: Promise<any>;
|
private pendingRead: Promise<any>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
24
yarn.lock
24
yarn.lock
@ -4052,6 +4052,11 @@ create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7:
|
|||||||
safe-buffer "^5.0.1"
|
safe-buffer "^5.0.1"
|
||||||
sha.js "^2.4.8"
|
sha.js "^2.4.8"
|
||||||
|
|
||||||
|
create-require@^1.1.0:
|
||||||
|
version "1.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333"
|
||||||
|
integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==
|
||||||
|
|
||||||
cross-fetch@^3.0.4:
|
cross-fetch@^3.0.4:
|
||||||
version "3.0.6"
|
version "3.0.6"
|
||||||
resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.0.6.tgz#3a4040bc8941e653e0e9cf17f29ebcd177d3365c"
|
resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.0.6.tgz#3a4040bc8941e653e0e9cf17f29ebcd177d3365c"
|
||||||
@ -13460,12 +13465,13 @@ ts-loader@^7.0.5:
|
|||||||
micromatch "^4.0.0"
|
micromatch "^4.0.0"
|
||||||
semver "^6.0.0"
|
semver "^6.0.0"
|
||||||
|
|
||||||
ts-node@^8.10.2:
|
ts-node@^9.1.1:
|
||||||
version "8.10.2"
|
version "9.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-8.10.2.tgz#eee03764633b1234ddd37f8db9ec10b75ec7fb8d"
|
resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-9.1.1.tgz#51a9a450a3e959401bda5f004a72d54b936d376d"
|
||||||
integrity sha512-ISJJGgkIpDdBhWVu3jufsWpK3Rzo7bdiIXJjQc0ynKxVOVcg2oIrf2H2cejminGrptVc6q6/uynAHNCuWGbpVA==
|
integrity sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==
|
||||||
dependencies:
|
dependencies:
|
||||||
arg "^4.1.0"
|
arg "^4.1.0"
|
||||||
|
create-require "^1.1.0"
|
||||||
diff "^4.0.1"
|
diff "^4.0.1"
|
||||||
make-error "^1.1.1"
|
make-error "^1.1.1"
|
||||||
source-map-support "^0.5.17"
|
source-map-support "^0.5.17"
|
||||||
@ -13618,16 +13624,16 @@ typeface-roboto@^0.0.75:
|
|||||||
resolved "https://registry.yarnpkg.com/typeface-roboto/-/typeface-roboto-0.0.75.tgz#98d5ba35ec234bbc7172374c8297277099cc712b"
|
resolved "https://registry.yarnpkg.com/typeface-roboto/-/typeface-roboto-0.0.75.tgz#98d5ba35ec234bbc7172374c8297277099cc712b"
|
||||||
integrity sha512-VrR/IiH00Z1tFP4vDGfwZ1esNqTiDMchBEXYY9kilT6wRGgFoCAlgkEUMHb1E3mB0FsfZhv756IF0+R+SFPfdg==
|
integrity sha512-VrR/IiH00Z1tFP4vDGfwZ1esNqTiDMchBEXYY9kilT6wRGgFoCAlgkEUMHb1E3mB0FsfZhv756IF0+R+SFPfdg==
|
||||||
|
|
||||||
typescript@4.0.2:
|
|
||||||
version "4.0.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.0.2.tgz#7ea7c88777c723c681e33bf7988be5d008d05ac2"
|
|
||||||
integrity sha512-e4ERvRV2wb+rRZ/IQeb3jm2VxBsirQLpQhdxplZ2MEzGvDkkMmPglecnNDfSUBivMjP93vRbngYYDQqQ/78bcQ==
|
|
||||||
|
|
||||||
typescript@^4.0.3:
|
typescript@^4.0.3:
|
||||||
version "4.1.2"
|
version "4.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.1.2.tgz#6369ef22516fe5e10304aae5a5c4862db55380e9"
|
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.1.2.tgz#6369ef22516fe5e10304aae5a5c4862db55380e9"
|
||||||
integrity sha512-thGloWsGH3SOxv1SoY7QojKi0tc+8FnOmiarEGMbd/lar7QOEd3hvlx3Fp5y6FlDUGl9L+pd4n2e+oToGMmhRQ==
|
integrity sha512-thGloWsGH3SOxv1SoY7QojKi0tc+8FnOmiarEGMbd/lar7QOEd3hvlx3Fp5y6FlDUGl9L+pd4n2e+oToGMmhRQ==
|
||||||
|
|
||||||
|
typescript@^4.2.3:
|
||||||
|
version "4.2.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.2.3.tgz#39062d8019912d43726298f09493d598048c1ce3"
|
||||||
|
integrity sha512-qOcYwxaByStAWrBf4x0fibwZvMRG+r4cQoTjbPtUlrWjBHbmCAww1i448U0GJ+3cNNEtebDteo/cHOR3xJ4wEw==
|
||||||
|
|
||||||
uglify-js@^3.1.4:
|
uglify-js@^3.1.4:
|
||||||
version "3.9.4"
|
version "3.9.4"
|
||||||
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.9.4.tgz#867402377e043c1fc7b102253a22b64e5862401b"
|
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.9.4.tgz#867402377e043c1fc7b102253a22b64e5862401b"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user