mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
subscribe for watching resources in single request when has admin-like access rights
Signed-off-by: Roman <ixrock@gmail.com>
This commit is contained in:
parent
bd9e44109a
commit
f91ce92666
@ -48,6 +48,7 @@ export interface ClusterState {
|
|||||||
isAdmin: boolean;
|
isAdmin: boolean;
|
||||||
allowedNamespaces: string[]
|
allowedNamespaces: string[]
|
||||||
allowedResources: string[]
|
allowedResources: string[]
|
||||||
|
watchApiAllEnabled: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -91,7 +92,6 @@ export class Cluster implements ClusterModel, ClusterState {
|
|||||||
*/
|
*/
|
||||||
@observable initializing = false;
|
@observable initializing = false;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Is cluster object initialized
|
* Is cluster object initialized
|
||||||
*
|
*
|
||||||
@ -177,6 +177,12 @@ export class Cluster implements ClusterModel, ClusterState {
|
|||||||
* @observable
|
* @observable
|
||||||
*/
|
*/
|
||||||
@observable isAdmin = false;
|
@observable isAdmin = false;
|
||||||
|
/**
|
||||||
|
* Does watch-api could be used for all resources, e.g. "/api/v1/services?watch=1"
|
||||||
|
*
|
||||||
|
* @observable
|
||||||
|
*/
|
||||||
|
@observable watchApiAllEnabled = false;
|
||||||
/**
|
/**
|
||||||
* Preferences
|
* Preferences
|
||||||
*
|
*
|
||||||
@ -353,9 +359,7 @@ export class Cluster implements ClusterModel, ClusterState {
|
|||||||
await this.refreshConnectionStatus();
|
await this.refreshConnectionStatus();
|
||||||
|
|
||||||
if (this.accessible) {
|
if (this.accessible) {
|
||||||
await this.refreshAllowedResources();
|
await this.refreshAccessibility();
|
||||||
this.isAdmin = await this.isClusterAdmin();
|
|
||||||
this.ready = true;
|
|
||||||
this.ensureKubectl();
|
this.ensureKubectl();
|
||||||
}
|
}
|
||||||
this.activated = true;
|
this.activated = true;
|
||||||
@ -410,13 +414,11 @@ export class Cluster implements ClusterModel, ClusterState {
|
|||||||
await this.refreshConnectionStatus();
|
await this.refreshConnectionStatus();
|
||||||
|
|
||||||
if (this.accessible) {
|
if (this.accessible) {
|
||||||
this.isAdmin = await this.isClusterAdmin();
|
await this.refreshAccessibility();
|
||||||
await this.refreshAllowedResources();
|
|
||||||
|
|
||||||
if (opts.refreshMetadata) {
|
if (opts.refreshMetadata) {
|
||||||
this.refreshMetadata();
|
this.refreshMetadata();
|
||||||
}
|
}
|
||||||
this.ready = true;
|
|
||||||
}
|
}
|
||||||
this.pushState();
|
this.pushState();
|
||||||
}
|
}
|
||||||
@ -433,6 +435,18 @@ export class Cluster implements ClusterModel, ClusterState {
|
|||||||
this.metadata = Object.assign(existingMetadata, metadata);
|
this.metadata = Object.assign(existingMetadata, metadata);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
async refreshAccessibility(): Promise<void> {
|
||||||
|
this.isAdmin = await this.isClusterAdmin();
|
||||||
|
this.watchApiAllEnabled = await this.canUseWatchApi({ resource: "*" });
|
||||||
|
|
||||||
|
await this.refreshAllowedResources();
|
||||||
|
|
||||||
|
this.ready = true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
@ -571,6 +585,17 @@ export class Cluster implements ClusterModel, ClusterState {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
async canUseWatchApi(customizeResource: V1ResourceAttributes = {}): Promise<boolean> {
|
||||||
|
return this.canI({
|
||||||
|
verb: "watch",
|
||||||
|
resource: "*",
|
||||||
|
...customizeResource,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
toJSON(): ClusterModel {
|
toJSON(): ClusterModel {
|
||||||
const model: ClusterModel = {
|
const model: ClusterModel = {
|
||||||
id: this.id,
|
id: this.id,
|
||||||
@ -604,6 +629,7 @@ export class Cluster implements ClusterModel, ClusterState {
|
|||||||
isAdmin: this.isAdmin,
|
isAdmin: this.isAdmin,
|
||||||
allowedNamespaces: this.allowedNamespaces,
|
allowedNamespaces: this.allowedNamespaces,
|
||||||
allowedResources: this.allowedResources,
|
allowedResources: this.allowedResources,
|
||||||
|
watchApiAllEnabled: this.watchApiAllEnabled,
|
||||||
};
|
};
|
||||||
|
|
||||||
return toJS(state, {
|
return toJS(state, {
|
||||||
|
|||||||
@ -8,8 +8,8 @@ import type { KubeObjectStore } from "../kube-object.store";
|
|||||||
|
|
||||||
import plimit from "p-limit";
|
import plimit from "p-limit";
|
||||||
import debounce from "lodash/debounce";
|
import debounce from "lodash/debounce";
|
||||||
import { autorun, comparer, computed, observable, reaction } from "mobx";
|
import { autorun, comparer, computed, IReactionDisposer, observable, reaction } from "mobx";
|
||||||
import { autobind, EventEmitter } from "../utils";
|
import { autobind, EventEmitter, noop } from "../utils";
|
||||||
import { ensureObjectSelfLink, KubeApi, parseKubeApi } from "./kube-api";
|
import { ensureObjectSelfLink, KubeApi, parseKubeApi } from "./kube-api";
|
||||||
import { KubeJsonApiData, KubeJsonApiError } from "./kube-json-api";
|
import { KubeJsonApiData, KubeJsonApiError } from "./kube-json-api";
|
||||||
import { apiPrefix, isDebugging, isProduction } from "../../common/vars";
|
import { apiPrefix, isDebugging, isProduction } from "../../common/vars";
|
||||||
@ -18,6 +18,7 @@ import { apiManager } from "./api-manager";
|
|||||||
export { IKubeWatchEvent, IKubeWatchEventStreamEnd };
|
export { IKubeWatchEvent, IKubeWatchEventStreamEnd };
|
||||||
|
|
||||||
export interface IKubeWatchMessage<T extends KubeObject = any> {
|
export interface IKubeWatchMessage<T extends KubeObject = any> {
|
||||||
|
namespace?: string;
|
||||||
data?: IKubeWatchEvent<KubeJsonApiData>
|
data?: IKubeWatchEvent<KubeJsonApiData>
|
||||||
error?: IKubeWatchEvent<KubeJsonApiError>;
|
error?: IKubeWatchEvent<KubeJsonApiError>;
|
||||||
api?: KubeApi<T>;
|
api?: KubeApi<T>;
|
||||||
@ -27,7 +28,7 @@ export interface IKubeWatchMessage<T extends KubeObject = any> {
|
|||||||
export interface IKubeWatchSubscribeStoreOptions {
|
export interface IKubeWatchSubscribeStoreOptions {
|
||||||
preload?: boolean; // preload store items, default: true
|
preload?: boolean; // preload store items, default: true
|
||||||
waitUntilLoaded?: boolean; // subscribe only after loading all stores, default: true
|
waitUntilLoaded?: boolean; // subscribe only after loading all stores, default: true
|
||||||
cacheLoading?: boolean; // when enabled loading store will be skipped, default: false
|
loadOnce?: boolean; // check store.isLoaded to skip loading if done already, default: false
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IKubeWatchReconnectOptions {
|
export interface IKubeWatchReconnectOptions {
|
||||||
@ -51,6 +52,12 @@ export class KubeWatchApi {
|
|||||||
@observable subscribers = observable.map<KubeApi, number>();
|
@observable subscribers = observable.map<KubeApi, number>();
|
||||||
@observable isConnected = false;
|
@observable isConnected = false;
|
||||||
|
|
||||||
|
@computed get watchAllEnabled(): boolean {
|
||||||
|
const { isAdmin, watchApiAllEnabled } = this?.cluster;
|
||||||
|
|
||||||
|
return Boolean(isAdmin || watchApiAllEnabled);
|
||||||
|
}
|
||||||
|
|
||||||
@computed get isReady(): boolean {
|
@computed get isReady(): boolean {
|
||||||
return Boolean(this.cluster && this.namespaces);
|
return Boolean(this.cluster && this.namespaces);
|
||||||
}
|
}
|
||||||
@ -69,11 +76,11 @@ export class KubeWatchApi {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (api.isNamespaced) {
|
if (api.isNamespaced && !this.watchAllEnabled) {
|
||||||
return this.namespaces.map(namespace => api.getWatchUrl(namespace));
|
return this.namespaces.map(namespace => api.getWatchUrl(namespace));
|
||||||
} else {
|
|
||||||
return api.getWatchUrl();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return api.getWatchUrl();
|
||||||
}).flat();
|
}).flat();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -127,36 +134,54 @@ export class KubeWatchApi {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
subscribeStores(stores: KubeObjectStore[], options: IKubeWatchSubscribeStoreOptions = {}): () => void {
|
preloadStores(stores: KubeObjectStore[], { loadOnce = false } = {}) {
|
||||||
const { preload = true, waitUntilLoaded = true, cacheLoading = false } = options;
|
|
||||||
const limitRequests = plimit(1); // load stores one by one to allow quick skipping when fast clicking btw pages
|
const limitRequests = plimit(1); // load stores one by one to allow quick skipping when fast clicking btw pages
|
||||||
const preloading: Promise<any>[] = [];
|
const preloading: Promise<any>[] = [];
|
||||||
|
|
||||||
|
for (const store of stores) {
|
||||||
|
preloading.push(limitRequests(async () => {
|
||||||
|
if (store.isLoaded && loadOnce) return; // skip
|
||||||
|
|
||||||
|
return store.loadAll(this.namespaces);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
loading: Promise.all(preloading),
|
||||||
|
cancelLoading: () => limitRequests.clearQueue(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
subscribeStores(stores: KubeObjectStore[], options: IKubeWatchSubscribeStoreOptions = {}): () => void {
|
||||||
|
const { preload = true, waitUntilLoaded = true, loadOnce = false } = options;
|
||||||
const apis = new Set(stores.map(store => store.getSubscribeApis()).flat());
|
const apis = new Set(stores.map(store => store.getSubscribeApis()).flat());
|
||||||
const unsubscribeList: (() => void)[] = [];
|
const unsubscribeList: (() => void)[] = [];
|
||||||
let isUnsubscribed = false;
|
let isUnsubscribed = false;
|
||||||
|
|
||||||
|
const load = () => this.preloadStores(stores, { loadOnce });
|
||||||
|
let preloading = preload && load();
|
||||||
|
let cancelReloading: IReactionDisposer = noop;
|
||||||
|
|
||||||
const subscribe = () => {
|
const subscribe = () => {
|
||||||
if (isUnsubscribed) return;
|
if (isUnsubscribed) return;
|
||||||
apis.forEach(api => unsubscribeList.push(this.subscribeApi(api)));
|
apis.forEach(api => unsubscribeList.push(this.subscribeApi(api)));
|
||||||
};
|
};
|
||||||
|
|
||||||
if (preload) {
|
if (preloading && waitUntilLoaded) {
|
||||||
for (const store of stores) {
|
preloading.loading.then(subscribe, error => {
|
||||||
preloading.push(limitRequests(async () => {
|
|
||||||
if (cacheLoading && store.isLoaded) return; // skip
|
|
||||||
|
|
||||||
return store.loadAll(this.namespaces);
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (waitUntilLoaded) {
|
|
||||||
Promise.all(preloading).then(subscribe, error => {
|
|
||||||
this.log({
|
this.log({
|
||||||
message: new Error("Loading stores has failed"),
|
message: new Error("Loading stores has failed"),
|
||||||
meta: { stores, error, options },
|
meta: { stores, error, options },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// reload when context namespaces changes
|
||||||
|
cancelReloading = reaction(() => this.namespaces, () => {
|
||||||
|
preloading?.cancelLoading();
|
||||||
|
preloading = load();
|
||||||
|
}, {
|
||||||
|
equals: comparer.shallow,
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
subscribe();
|
subscribe();
|
||||||
}
|
}
|
||||||
@ -165,7 +190,8 @@ export class KubeWatchApi {
|
|||||||
return () => {
|
return () => {
|
||||||
if (isUnsubscribed) return;
|
if (isUnsubscribed) return;
|
||||||
isUnsubscribed = true;
|
isUnsubscribed = true;
|
||||||
limitRequests.clearQueue();
|
cancelReloading();
|
||||||
|
preloading?.cancelLoading();
|
||||||
unsubscribeList.forEach(unsubscribe => unsubscribe());
|
unsubscribeList.forEach(unsubscribe => unsubscribe());
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -252,6 +278,10 @@ export class KubeWatchApi {
|
|||||||
const kubeEvent: IKubeWatchEvent = JSON.parse(json);
|
const kubeEvent: IKubeWatchEvent = JSON.parse(json);
|
||||||
const message = this.getMessage(kubeEvent);
|
const message = this.getMessage(kubeEvent);
|
||||||
|
|
||||||
|
if (!this.namespaces.includes(message.namespace)) {
|
||||||
|
continue; // skip updates from non-watching resources context
|
||||||
|
}
|
||||||
|
|
||||||
this.onMessage.emit(message);
|
this.onMessage.emit(message);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return json;
|
return json;
|
||||||
@ -284,6 +314,7 @@ export class KubeWatchApi {
|
|||||||
|
|
||||||
message.api = api;
|
message.api = api;
|
||||||
message.store = apiManager.getStore(api);
|
message.store = apiManager.getStore(api);
|
||||||
|
message.namespace = namespace;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user