Commit 2e2220cb authored by Jari Kolehmainen's avatar Jari Kolehmainen
Browse files

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


* mark cluster & workspace as beta
Signed-off-by: default avatarJari Kolehmainen <jari.kolehmainen@gmail.com>

* back to public
Signed-off-by: default avatarJari Kolehmainen <jari.kolehmainen@gmail.com>
parent a5b31069
Showing with 253 additions and 26 deletions
+253 -26
......@@ -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);
}
......
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";
......@@ -9,6 +9,8 @@ export type { ClusterModel, ClusterId } from "../../common/cluster-store";
/**
* Store for all added clusters
*
* @beta
*/
export class ClusterStore extends Singleton {
......
......@@ -7,6 +7,8 @@ export type { WorkspaceId, WorkspaceModel } from "../../common/workspace-store";
/**
* Stores all workspaces
*
* @beta
*/
export class WorkspaceStore extends Singleton {
/**
......
......@@ -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<boolean> {
const authApi = this.getProxyKubeconfig().makeApiClient(AuthorizationV1Api);
......@@ -347,6 +537,9 @@ export class Cluster implements ClusterModel, ClusterState {
}
}
/**
* @internal
*/
async isClusterAdmin(): Promise<boolean> {
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);
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment