diff --git a/src/common/workspace-store.ts b/src/common/workspace-store.ts index e1fa113ca3..7688516af2 100644 --- a/src/common/workspace-store.ts +++ b/src/common/workspace-store.ts @@ -26,6 +26,11 @@ export interface WorkspaceState { enabled: boolean; } +/** + * Workspace + * + * @beta + */ export class Workspace implements WorkspaceModel, WorkspaceState { /** * Unique id for workspace @@ -78,23 +83,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 toJS({ 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 988302543e..b2aba41c06 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 a130691e8e..79e28fa9c4 100644 --- a/src/main/cluster.ts +++ b/src/main/cluster.ts @@ -49,10 +49,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[] = []; @@ -61,34 +82,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; @@ -97,6 +231,9 @@ export class Cluster implements ClusterModel, ClusterState { }); } + /** + * Kubernetes version + */ get version(): string { return String(this.metadata?.version) || ""; } @@ -110,17 +247,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); @@ -139,6 +288,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 @@ -156,14 +308,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(); } @@ -190,22 +348,29 @@ 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(); @@ -217,8 +382,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(); @@ -235,8 +403,10 @@ export class Cluster implements ClusterModel, ClusterState { this.pushState(); } - @action - async refreshMetadata() { + /** + * @internal + */ + @action async refreshMetadata() { logger.info(`[CLUSTER]: refreshMetadata`, this.getMeta()); const metadata = await detectorRegistry.detectForCluster(this); const existingMetadata = this.metadata; @@ -244,16 +414,20 @@ export class Cluster implements ClusterModel, ClusterState { 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() { + /** + * @internal + */ + @action async refreshAllowedResources() { this.allowedNamespaces = await this.getAllowedNamespaces(); this.allowedResources = await this.getAllowedResources(); } @@ -262,10 +436,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(); } @@ -279,6 +459,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`; @@ -329,6 +515,10 @@ export class Cluster implements ClusterModel, ClusterState { } } + /** + * @internal + * @param resourceAttributes resource attributes + */ async canI(resourceAttributes: V1ResourceAttributes): Promise { const authApi = this.getProxyKubeconfig().makeApiClient(AuthorizationV1Api); @@ -347,6 +537,9 @@ export class Cluster implements ClusterModel, ClusterState { } } + /** + * @internal + */ async isClusterAdmin(): Promise { return this.canI({ namespace: "kube-system", @@ -372,7 +565,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, @@ -393,11 +588,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);