1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

Tag cluster & workspace as beta features in extension api (#1589)

* mark cluster & workspace as beta

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* back to public

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>
This commit is contained in:
Jari Kolehmainen 2020-12-07 13:38:40 +02:00 committed by GitHub
parent d3026d4dd0
commit 8f27559053
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 253 additions and 26 deletions

View File

@ -26,6 +26,11 @@ export interface WorkspaceState {
enabled: boolean; enabled: boolean;
} }
/**
* Workspace
*
* @beta
*/
export class Workspace implements WorkspaceModel, WorkspaceState { export class Workspace implements WorkspaceModel, WorkspaceState {
/** /**
* Unique id for workspace * Unique id for workspace
@ -78,23 +83,39 @@ export class Workspace implements WorkspaceModel, WorkspaceState {
} }
} }
/**
* Is workspace managed by an extension
*/
get isManaged(): boolean { get isManaged(): boolean {
return !!this.ownerRef; return !!this.ownerRef;
} }
/**
* Get workspace state
*
*/
getState(): WorkspaceState { getState(): WorkspaceState {
return toJS({ return toJS({
enabled: this.enabled enabled: this.enabled
}); });
} }
/**
* Push state
*
* @interal
* @param state workspace state
*/
pushState(state = this.getState()) { pushState(state = this.getState()) {
logger.silly("[WORKSPACE] pushing state", {...state, id: this.id}); logger.silly("[WORKSPACE] pushing state", {...state, id: this.id});
broadcastMessage("workspace:state", this.id, toJS(state)); broadcastMessage("workspace:state", this.id, toJS(state));
} }
@action /**
setState(state: WorkspaceState) { *
* @param state workspace state
*/
@action setState(state: WorkspaceState) {
Object.assign(this, state); Object.assign(this, state);
} }

View File

@ -1,8 +1,8 @@
export { ExtensionStore } from "../extension-store"; 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 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"; export type { WorkspaceId, WorkspaceModel } from "../stores/workspace-store";

View File

@ -9,6 +9,8 @@ export type { ClusterModel, ClusterId } from "../../common/cluster-store";
/** /**
* Store for all added clusters * Store for all added clusters
*
* @beta
*/ */
export class ClusterStore extends Singleton { export class ClusterStore extends Singleton {

View File

@ -7,6 +7,8 @@ export type { WorkspaceId, WorkspaceModel } from "../../common/workspace-store";
/** /**
* Stores all workspaces * Stores all workspaces
*
* @beta
*/ */
export class WorkspaceStore extends Singleton { export class WorkspaceStore extends Singleton {
/** /**

View File

@ -49,10 +49,31 @@ export interface ClusterState {
allowedResources: string[] allowedResources: string[]
} }
/**
* Cluster
*
* @beta
*/
export class Cluster implements ClusterModel, ClusterState { export class Cluster implements ClusterModel, ClusterState {
/** Unique id for a cluster */
public id: ClusterId; public id: ClusterId;
/**
* Kubectl
*
* @internal
*/
public kubeCtl: Kubectl; public kubeCtl: Kubectl;
/**
* Context handler
*
* @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; public ownerRef: string;
protected kubeconfigManager: KubeconfigManager; protected kubeconfigManager: KubeconfigManager;
protected eventDisposers: Function[] = []; protected eventDisposers: Function[] = [];
@ -61,34 +82,147 @@ export class Cluster implements ClusterModel, ClusterState {
whenInitialized = when(() => this.initialized); whenInitialized = when(() => this.initialized);
whenReady = when(() => this.ready); whenReady = when(() => this.ready);
/**
* Is cluster object initialized
*
* @observable
*/
@observable initialized = false; @observable initialized = false;
/**
* Kubeconfig context name
*
* @observable
*/
@observable contextName: string; @observable contextName: string;
/**
* Workspace id
*
* @observable
*/
@observable workspace: WorkspaceId; @observable workspace: WorkspaceId;
/**
* Path to kubeconfig
*
* @observable
*/
@observable kubeConfigPath: string; @observable kubeConfigPath: string;
/**
* Kubernetes API server URL
*
* @observable
*/
@observable apiUrl: string; // cluster server url @observable apiUrl: string; // cluster server url
/**
* Internal authentication proxy URL
*
* @observable
* @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 @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 @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 @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 @observable ready = false; // cluster is in usable state
/**
* Is cluster currently reconnecting
*
* @observable
*/
@observable reconnecting = false; @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; @observable failureReason: string;
/**
* Does user have admin like access
*
* @observable
*/
@observable isAdmin = false; @observable isAdmin = false;
/**
* Preferences
*
* @observable
*/
@observable preferences: ClusterPreferences = {}; @observable preferences: ClusterPreferences = {};
/**
* Metadata
*
* @observable
*/
@observable metadata: ClusterMetadata = {}; @observable metadata: ClusterMetadata = {};
/**
* List of allowed namespaces
*
* @observable
*/
@observable allowedNamespaces: string[] = []; @observable allowedNamespaces: string[] = [];
/**
* List of allowed resources
*
* @observable
* @internal
*/
@observable allowedResources: string[] = []; @observable allowedResources: string[] = [];
/**
* List of accessible namespaces
*
* @observable
*/
@observable accessibleNamespaces: string[] = []; @observable accessibleNamespaces: string[] = [];
/**
* Is cluster available
*
* @computed
*/
@computed get available() { @computed get available() {
return this.accessible && !this.disconnected; return this.accessible && !this.disconnected;
} }
/**
* Cluster name
*
* @computed
*/
@computed get name() { @computed get name() {
return this.preferences.clusterName || this.contextName; return this.preferences.clusterName || this.contextName;
} }
/**
* Prometheus preferences
*
* @computed
* @internal
*/
@computed get prometheusPreferences(): ClusterPrometheusPreferences { @computed get prometheusPreferences(): ClusterPrometheusPreferences {
const { prometheus, prometheusProvider } = this.preferences; const { prometheus, prometheusProvider } = this.preferences;
@ -97,6 +231,9 @@ export class Cluster implements ClusterModel, ClusterState {
}); });
} }
/**
* Kubernetes version
*/
get version(): string { get version(): string {
return String(this.metadata?.version) || ""; return String(this.metadata?.version) || "";
} }
@ -110,17 +247,29 @@ export class Cluster implements ClusterModel, ClusterState {
} }
} }
/**
* Is cluster managed by an extension
*/
get isManaged(): boolean { get isManaged(): boolean {
return !!this.ownerRef; return !!this.ownerRef;
} }
@action /**
updateModel(model: ClusterModel) { * Update cluster data model
*
* @param model
*/
@action updateModel(model: ClusterModel) {
Object.assign(this, model); 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 { try {
this.contextHandler = new ContextHandler(this); this.contextHandler = new ContextHandler(this);
this.kubeconfigManager = await KubeconfigManager.create(this, this.contextHandler, port); this.kubeconfigManager = await KubeconfigManager.create(this, this.contextHandler, port);
@ -139,6 +288,9 @@ export class Cluster implements ClusterModel, ClusterState {
} }
} }
/**
* @internal
*/
protected bindEvents() { protected bindEvents() {
logger.info(`[CLUSTER]: bind events`, this.getMeta()); logger.info(`[CLUSTER]: bind events`, this.getMeta());
const refreshTimer = setInterval(() => !this.disconnected && this.refresh(), 30000); // every 30s const refreshTimer = setInterval(() => !this.disconnected && this.refresh(), 30000); // every 30s
@ -156,14 +308,20 @@ export class Cluster implements ClusterModel, ClusterState {
} }
} }
/**
* internal
*/
protected unbindEvents() { protected unbindEvents() {
logger.info(`[CLUSTER]: unbind events`, this.getMeta()); logger.info(`[CLUSTER]: unbind events`, this.getMeta());
this.eventDisposers.forEach(dispose => dispose()); this.eventDisposers.forEach(dispose => dispose());
this.eventDisposers.length = 0; this.eventDisposers.length = 0;
} }
@action /**
async activate(force = false) { * @param force force activation
* @internal
*/
@action async activate(force = false) {
if (this.activated && !force) { if (this.activated && !force) {
return this.pushState(); return this.pushState();
} }
@ -190,22 +348,29 @@ export class Cluster implements ClusterModel, ClusterState {
return this.pushState(); return this.pushState();
} }
/**
* @internal
*/
protected async ensureKubectl() { protected async ensureKubectl() {
this.kubeCtl = new Kubectl(this.version); this.kubeCtl = new Kubectl(this.version);
return this.kubeCtl.ensureKubectl(); // download kubectl in background, so it's not blocking dashboard 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()); logger.info(`[CLUSTER]: reconnect`, this.getMeta());
this.contextHandler?.stopServer(); this.contextHandler?.stopServer();
await this.contextHandler?.ensureServer(); await this.contextHandler?.ensureServer();
this.disconnected = false; this.disconnected = false;
} }
@action /**
disconnect() { * @internal
*/
@action disconnect() {
logger.info(`[CLUSTER]: disconnect`, this.getMeta()); logger.info(`[CLUSTER]: disconnect`, this.getMeta());
this.unbindEvents(); this.unbindEvents();
this.contextHandler?.stopServer(); this.contextHandler?.stopServer();
@ -217,8 +382,11 @@ export class Cluster implements ClusterModel, ClusterState {
this.pushState(); this.pushState();
} }
@action /**
async refresh(opts: ClusterRefreshOptions = {}) { * @internal
* @param opts refresh options
*/
@action async refresh(opts: ClusterRefreshOptions = {}) {
logger.info(`[CLUSTER]: refresh`, this.getMeta()); logger.info(`[CLUSTER]: refresh`, this.getMeta());
await this.whenInitialized; await this.whenInitialized;
await this.refreshConnectionStatus(); await this.refreshConnectionStatus();
@ -235,8 +403,10 @@ export class Cluster implements ClusterModel, ClusterState {
this.pushState(); this.pushState();
} }
@action /**
async refreshMetadata() { * @internal
*/
@action async refreshMetadata() {
logger.info(`[CLUSTER]: refreshMetadata`, this.getMeta()); logger.info(`[CLUSTER]: refreshMetadata`, this.getMeta());
const metadata = await detectorRegistry.detectForCluster(this); const metadata = await detectorRegistry.detectForCluster(this);
const existingMetadata = this.metadata; const existingMetadata = this.metadata;
@ -244,16 +414,20 @@ export class Cluster implements ClusterModel, ClusterState {
this.metadata = Object.assign(existingMetadata, metadata); this.metadata = Object.assign(existingMetadata, metadata);
} }
@action /**
async refreshConnectionStatus() { * @internal
*/
@action async refreshConnectionStatus() {
const connectionStatus = await this.getConnectionStatus(); const connectionStatus = await this.getConnectionStatus();
this.online = connectionStatus > ClusterStatus.Offline; this.online = connectionStatus > ClusterStatus.Offline;
this.accessible = connectionStatus == ClusterStatus.AccessGranted; this.accessible = connectionStatus == ClusterStatus.AccessGranted;
} }
@action /**
async refreshAllowedResources() { * @internal
*/
@action async refreshAllowedResources() {
this.allowedNamespaces = await this.getAllowedNamespaces(); this.allowedNamespaces = await this.getAllowedNamespaces();
this.allowedResources = await this.getAllowedResources(); this.allowedResources = await this.getAllowedResources();
} }
@ -262,10 +436,16 @@ export class Cluster implements ClusterModel, ClusterState {
return loadConfig(this.kubeConfigPath); return loadConfig(this.kubeConfigPath);
} }
/**
* @internal
*/
getProxyKubeconfig(): KubeConfig { getProxyKubeconfig(): KubeConfig {
return loadConfig(this.getProxyKubeconfigPath()); return loadConfig(this.getProxyKubeconfigPath());
} }
/**
* @internal
*/
getProxyKubeconfigPath(): string { getProxyKubeconfigPath(): string {
return this.kubeconfigManager.getPath(); return this.kubeconfigManager.getPath();
} }
@ -279,6 +459,12 @@ export class Cluster implements ClusterModel, ClusterState {
return request(this.kubeProxyUrl + path, options); return request(this.kubeProxyUrl + path, options);
} }
/**
*
* @param prometheusPath path to prometheus service
* @param queryParams query parameters
* @internal
*/
getMetrics(prometheusPath: string, queryParams: IMetricsReqParams & { query: string }) { getMetrics(prometheusPath: string, queryParams: IMetricsReqParams & { query: string }) {
const prometheusPrefix = this.preferences.prometheus?.prefix || ""; const prometheusPrefix = this.preferences.prometheus?.prefix || "";
const metricsPath = `/api/v1/namespaces/${prometheusPath}/proxy${prometheusPrefix}/api/v1/query_range`; 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<boolean> { async canI(resourceAttributes: V1ResourceAttributes): Promise<boolean> {
const authApi = this.getProxyKubeconfig().makeApiClient(AuthorizationV1Api); const authApi = this.getProxyKubeconfig().makeApiClient(AuthorizationV1Api);
@ -347,6 +537,9 @@ export class Cluster implements ClusterModel, ClusterState {
} }
} }
/**
* @internal
*/
async isClusterAdmin(): Promise<boolean> { async isClusterAdmin(): Promise<boolean> {
return this.canI({ return this.canI({
namespace: "kube-system", 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 { getState(): ClusterState {
const state: ClusterState = { const state: ClusterState = {
initialized: this.initialized, 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); Object.assign(this, state);
} }
/**
* @internal
* @param state cluster state
*/
pushState(state = this.getState()) { pushState(state = this.getState()) {
logger.silly(`[CLUSTER]: push-state`, state); logger.silly(`[CLUSTER]: push-state`, state);
broadcastMessage("cluster:state", this.id, state); broadcastMessage("cluster:state", this.id, state);