From 350f87e065704f6caa04c3260b8fae2c8fbf6822 Mon Sep 17 00:00:00 2001 From: Jari Kolehmainen Date: Tue, 1 Dec 2020 12:29:58 +0200 Subject: [PATCH] mark cluster & workspace as beta Signed-off-by: Jari Kolehmainen --- src/common/workspace-store.ts | 47 ++++- src/extensions/core-api/stores.ts | 4 +- src/extensions/stores/cluster-store.ts | 2 + src/extensions/stores/workspace-store.ts | 2 + src/main/cluster.ts | 235 ++++++++++++++++++++--- 5 files changed, 264 insertions(+), 26 deletions(-) diff --git a/src/common/workspace-store.ts b/src/common/workspace-store.ts index 75ab36f19e..3827ce0e2c 100644 --- a/src/common/workspace-store.ts +++ b/src/common/workspace-store.ts @@ -26,11 +26,38 @@ export interface WorkspaceState { enabled: boolean; } +/** + * Workspace + * + * @beta + */ export class Workspace implements WorkspaceModel, WorkspaceState { + /** + * Unique id + * @observable + */ @observable id: WorkspaceId; + /** + * Workspace name + * @observable + */ @observable name: string; + /** + * Description + * @observable + */ @observable description?: string; + /** + * Owner reference + * + * If an extension sets this then extension also needs explicitly to set workspace as enabled + * @observable + */ @observable ownerRef?: string; + /** + * Is workspace enabled (disabled workspaces are currently hidden) + * @observable + */ @observable enabled: boolean; @observable lastActiveClusterId?: ClusterId; @@ -44,23 +71,39 @@ export class Workspace implements WorkspaceModel, WorkspaceState { } } + /** + * Is workspace managed by an extension + */ get isManaged(): boolean { return !!this.ownerRef; } + /** + * Get workspace state + * + */ getState(): WorkspaceState { return { enabled: this.enabled }; } + /** + * Push state + * + * @interal + * @param state workspace state + */ pushState(state = this.getState()) { logger.silly("[WORKSPACE] pushing state", {...state, id: this.id}); broadcastMessage("workspace:state", this.id, toJS(state)); } - @action - setState(state: WorkspaceState) { + /** + * + * @param state workspace state + */ + @action setState(state: WorkspaceState) { Object.assign(this, state); } diff --git a/src/extensions/core-api/stores.ts b/src/extensions/core-api/stores.ts index aa14623287..706c336fdf 100644 --- a/src/extensions/core-api/stores.ts +++ b/src/extensions/core-api/stores.ts @@ -1,8 +1,8 @@ export { ExtensionStore } from "../extension-store"; -export { clusterStore, Cluster } from "../stores/cluster-store"; +export { clusterStore, Cluster, ClusterStore } from "../stores/cluster-store"; export type { ClusterModel, ClusterId } from "../stores/cluster-store"; -export { workspaceStore, Workspace } from "../stores/workspace-store"; +export { workspaceStore, Workspace, WorkspaceStore } from "../stores/workspace-store"; export type { WorkspaceId, WorkspaceModel } from "../stores/workspace-store"; diff --git a/src/extensions/stores/cluster-store.ts b/src/extensions/stores/cluster-store.ts index 60c409b525..f074ef4a1a 100644 --- a/src/extensions/stores/cluster-store.ts +++ b/src/extensions/stores/cluster-store.ts @@ -9,6 +9,8 @@ export type { ClusterModel, ClusterId } from "../../common/cluster-store"; /** * Store for all added clusters + * + * @beta */ export class ClusterStore extends Singleton { diff --git a/src/extensions/stores/workspace-store.ts b/src/extensions/stores/workspace-store.ts index dd5f8c9ebd..2ff4a830fd 100644 --- a/src/extensions/stores/workspace-store.ts +++ b/src/extensions/stores/workspace-store.ts @@ -7,6 +7,8 @@ export type { WorkspaceId, WorkspaceModel } from "../../common/workspace-store"; /** * Stores all workspaces + * + * @beta */ export class WorkspaceStore extends Singleton { /** diff --git a/src/main/cluster.ts b/src/main/cluster.ts index 5ee7457529..ce5abb0602 100644 --- a/src/main/cluster.ts +++ b/src/main/cluster.ts @@ -48,10 +48,31 @@ export interface ClusterState { allowedResources: string[] } +/** + * Cluster + * + * @beta + */ export class Cluster implements ClusterModel, ClusterState { + /** Unique id for a cluster */ public id: ClusterId; + /** + * Kubectl + * + * @internal + */ public kubeCtl: Kubectl; + /** + * Context handler + * + * @internal + */ 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 eventDisposers: Function[] = []; @@ -60,34 +81,147 @@ export class Cluster implements ClusterModel, ClusterState { whenInitialized = when(() => this.initialized); whenReady = when(() => this.ready); + /** + * Is cluster object initialized + * + * @observable + */ @observable initialized = false; + /** + * Kubeconfig context name + * + * @observable + */ @observable contextName: string; + /** + * Workspace id + * + * @observable + */ @observable workspace: WorkspaceId; + /** + * Path to kubeconfig + * + * @observable + */ @observable kubeConfigPath: string; + /** + * Kubernetes API server URL + * + * @observable + */ @observable apiUrl: string; // cluster server url + /** + * Internal authentication proxy URL + * + * @observable + * @internal + */ @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 + * + * @observable + */ @observable online = false; // describes if we can detect that cluster is online + /** + * Can user access cluster resources + * + * @observable + */ @observable accessible = false; // if user is able to access cluster resources + /** + * Is cluster instance in usable state + * + * @observable + */ @observable ready = false; // cluster is in usable state + /** + * Is cluster currently reconnecting + * + * @observable + */ @observable reconnecting = false; - @observable disconnected = true; // false if user has selected to connect + /** + * Is cluster disconnected. False if user has selected to connect. + * + * @observable + */ + @observable disconnected = true; + /** + * Connection failure reason + * + * @observable + */ @observable failureReason: string; + /** + * Does user have admin like access + * + * @observable + */ @observable isAdmin = false; + /** + * Preferences + * + * @observable + */ @observable preferences: ClusterPreferences = {}; + /** + * Metadata + * + * @observable + */ @observable metadata: ClusterMetadata = {}; + /** + * List of allowed namespaces + * + * @observable + */ @observable allowedNamespaces: string[] = []; + /** + * List of allowed resources + * + * @observable + * @internal + */ @observable allowedResources: string[] = []; + /** + * List of accessible namespaces + * + * @observable + */ @observable accessibleNamespaces: string[] = []; + /** + * Is cluster available + * + * @computed + */ @computed get available() { return this.accessible && !this.disconnected; } + /** + * Cluster name + * + * @computed + */ @computed get name() { return this.preferences.clusterName || this.contextName; } + /** + * Prometheus preferences + * + * @computed + * @internal + */ @computed get prometheusPreferences(): ClusterPrometheusPreferences { const { prometheus, prometheusProvider } = this.preferences; return toJS({ prometheus, prometheusProvider }, { @@ -95,6 +229,9 @@ export class Cluster implements ClusterModel, ClusterState { }); } + /** + * Kubernetes version + */ get version(): string { return String(this.metadata?.version) || ""; } @@ -107,17 +244,29 @@ export class Cluster implements ClusterModel, ClusterState { } } + /** + * Is cluster managed by an extension + */ get isManaged(): boolean { return !!this.ownerRef; } - @action - updateModel(model: ClusterModel) { + /** + * Update cluster data model + * + * @param model + */ + @action updateModel(model: ClusterModel) { Object.assign(this, model); } - @action - async init(port: number) { + /** + * Initialize a cluster (can be done only in main process) + * + * @param port port where internal auth proxy is listening + * @internal + */ + @action async init(port: number) { try { this.contextHandler = new ContextHandler(this); this.kubeconfigManager = await KubeconfigManager.create(this, this.contextHandler, port); @@ -136,6 +285,9 @@ export class Cluster implements ClusterModel, ClusterState { } } + /** + * @internal + */ protected bindEvents() { logger.info(`[CLUSTER]: bind events`, this.getMeta()); const refreshTimer = setInterval(() => !this.disconnected && this.refresh(), 30000); // every 30s @@ -153,14 +305,20 @@ export class Cluster implements ClusterModel, ClusterState { } } + /** + * internal + */ protected unbindEvents() { logger.info(`[CLUSTER]: unbind events`, this.getMeta()); this.eventDisposers.forEach(dispose => dispose()); this.eventDisposers.length = 0; } - @action - async activate(force = false) { + /** + * @param force force activation + * @internal + */ + @action async activate(force = false) { if (this.activated && !force) { return this.pushState(); } @@ -183,21 +341,28 @@ export class Cluster implements ClusterModel, ClusterState { return this.pushState(); } + /** + * @internal + */ protected async ensureKubectl() { this.kubeCtl = new Kubectl(this.version); return this.kubeCtl.ensureKubectl(); // download kubectl in background, so it's not blocking dashboard } - @action - async reconnect() { + /** + * @internal + */ + @action async reconnect() { logger.info(`[CLUSTER]: reconnect`, this.getMeta()); this.contextHandler?.stopServer(); await this.contextHandler?.ensureServer(); this.disconnected = false; } - @action - disconnect() { + /** + * @internal + */ + @action disconnect() { logger.info(`[CLUSTER]: disconnect`, this.getMeta()); this.unbindEvents(); this.contextHandler?.stopServer(); @@ -209,8 +374,11 @@ export class Cluster implements ClusterModel, ClusterState { this.pushState(); } - @action - async refresh(opts: ClusterRefreshOptions = {}) { + /** + * @internal + * @param opts refresh options + */ + @action async refresh(opts: ClusterRefreshOptions = {}) { logger.info(`[CLUSTER]: refresh`, this.getMeta()); await this.whenInitialized; await this.refreshConnectionStatus(); @@ -226,22 +394,24 @@ export class Cluster implements ClusterModel, ClusterState { } @action - async refreshMetadata() { + protected async refreshMetadata() { logger.info(`[CLUSTER]: refreshMetadata`, this.getMeta()); const metadata = await detectorRegistry.detectForCluster(this); const existingMetadata = this.metadata; this.metadata = Object.assign(existingMetadata, metadata); } - @action - async refreshConnectionStatus() { + /** + * @internal + */ + @action async refreshConnectionStatus() { const connectionStatus = await this.getConnectionStatus(); this.online = connectionStatus > ClusterStatus.Offline; this.accessible = connectionStatus == ClusterStatus.AccessGranted; } @action - async refreshAllowedResources() { + protected async refreshAllowedResources() { this.allowedNamespaces = await this.getAllowedNamespaces(); this.allowedResources = await this.getAllowedResources(); } @@ -250,10 +420,16 @@ export class Cluster implements ClusterModel, ClusterState { return loadConfig(this.kubeConfigPath); } + /** + * @internal + */ getProxyKubeconfig(): KubeConfig { return loadConfig(this.getProxyKubeconfigPath()); } + /** + * @internal + */ getProxyKubeconfigPath(): string { return this.kubeconfigManager.getPath(); } @@ -267,6 +443,12 @@ export class Cluster implements ClusterModel, ClusterState { return request(this.kubeProxyUrl + path, options); } + /** + * + * @param prometheusPath path to prometheus service + * @param queryParams query parameters + * @internal + */ getMetrics(prometheusPath: string, queryParams: IMetricsReqParams & { query: string }) { const prometheusPrefix = this.preferences.prometheus?.prefix || ""; const metricsPath = `/api/v1/namespaces/${prometheusPath}/proxy${prometheusPrefix}/api/v1/query_range`; @@ -308,7 +490,7 @@ export class Cluster implements ClusterModel, ClusterState { } } - async canI(resourceAttributes: V1ResourceAttributes): Promise { + protected async canI(resourceAttributes: V1ResourceAttributes): Promise { const authApi = this.getProxyKubeconfig().makeApiClient(AuthorizationV1Api); try { const accessReview = await authApi.createSelfSubjectAccessReview({ @@ -323,7 +505,7 @@ export class Cluster implements ClusterModel, ClusterState { } } - async isClusterAdmin(): Promise { + protected async isClusterAdmin(): Promise { return this.canI({ namespace: "kube-system", resource: "*", @@ -347,7 +529,9 @@ export class Cluster implements ClusterModel, ClusterState { }); } - // serializable cluster-state used for sync btw main <-> renderer + /** + * Serializable cluster-state used for sync btw main <-> renderer + */ getState(): ClusterState { const state: ClusterState = { initialized: this.initialized, @@ -366,11 +550,18 @@ export class Cluster implements ClusterModel, ClusterState { }); } - @action - setState(state: ClusterState) { + /** + * @internal + * @param state cluster state + */ + @action setState(state: ClusterState) { Object.assign(this, state); } + /** + * @internal + * @param state cluster state + */ pushState(state = this.getState()) { logger.silly(`[CLUSTER]: push-state`, state); broadcastMessage("cluster:state", this.id, state);