mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Merge branch 'master' into allow-to-import-app-as-lib
Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>
This commit is contained in:
commit
d1c515f6ff
@ -3,7 +3,7 @@
|
||||
"productName": "OpenLens",
|
||||
"description": "OpenLens - Open Source IDE for Kubernetes",
|
||||
"homepage": "https://github.com/lensapp/lens",
|
||||
"version": "6.3.0-alpha.0",
|
||||
"version": "6.4.0-alpha.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/lensapp/lens.git"
|
||||
|
||||
@ -26,7 +26,7 @@ import type { Logger } from "../logger";
|
||||
import type { BroadcastMessage } from "../ipc/broadcast-message.injectable";
|
||||
import type { LoadConfigfromFile } from "../kube-helpers/load-config-from-file.injectable";
|
||||
import type { CanListResource, RequestNamespaceListPermissions, RequestNamespaceListPermissionsFor } from "./request-namespace-list-permissions.injectable";
|
||||
import type { RequestApiResources } from "./request-api-resources.injectable";
|
||||
import type { RequestApiResources } from "../../main/cluster/request-api-resources.injectable";
|
||||
|
||||
export interface ClusterDependencies {
|
||||
readonly directoryForKubeConfigs: string;
|
||||
|
||||
@ -1,83 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { V1APIGroupList, V1APIResourceList, V1APIVersions } from "@kubernetes/client-node";
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import k8SRequestInjectable from "../../main/k8s-request.injectable";
|
||||
import loggerInjectable from "../logger.injectable";
|
||||
import type { KubeApiResource } from "../rbac";
|
||||
import type { Cluster } from "./cluster";
|
||||
import plimit from "p-limit";
|
||||
|
||||
export type RequestApiResources = (cluster: Cluster) => Promise<KubeApiResource[]>;
|
||||
|
||||
interface KubeResourceListGroup {
|
||||
group: string;
|
||||
path: string;
|
||||
}
|
||||
|
||||
const requestApiResourcesInjectable = getInjectable({
|
||||
id: "request-api-resources",
|
||||
instantiate: (di): RequestApiResources => {
|
||||
const k8sRequest = di.inject(k8SRequestInjectable);
|
||||
const logger = di.inject(loggerInjectable);
|
||||
|
||||
return async (cluster) => {
|
||||
const apiLimit = plimit(5);
|
||||
const kubeApiResources: KubeApiResource[] = [];
|
||||
const resourceListGroups: KubeResourceListGroup[] = [];
|
||||
|
||||
try {
|
||||
await Promise.all([
|
||||
(async () => {
|
||||
const { versions } = await k8sRequest(cluster, "/api") as V1APIVersions;
|
||||
|
||||
for (const version of versions) {
|
||||
resourceListGroups.push({
|
||||
group: version,
|
||||
path: `/api/${version}`,
|
||||
});
|
||||
}
|
||||
})(),
|
||||
(async () => {
|
||||
const { groups } = await k8sRequest(cluster, "/apis") as V1APIGroupList;
|
||||
|
||||
for (const { preferredVersion, name } of groups) {
|
||||
const { groupVersion } = preferredVersion ?? {};
|
||||
|
||||
if (groupVersion) {
|
||||
resourceListGroups.push({
|
||||
group: name,
|
||||
path: `/apis/${groupVersion}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
})(),
|
||||
]);
|
||||
|
||||
await Promise.all(
|
||||
resourceListGroups.map(({ group, path }) => apiLimit(async () => {
|
||||
const { resources } = await k8sRequest(cluster, path) as V1APIResourceList;
|
||||
|
||||
for (const resource of resources) {
|
||||
kubeApiResources.push({
|
||||
apiName: resource.name,
|
||||
kind: resource.kind,
|
||||
group,
|
||||
namespaced: resource.namespaced,
|
||||
});
|
||||
}
|
||||
})),
|
||||
);
|
||||
} catch (error) {
|
||||
logger.error(`[LIST-API-RESOURCES]: failed to list api resources: ${error}`);
|
||||
}
|
||||
|
||||
return kubeApiResources;
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default requestApiResourcesInjectable;
|
||||
@ -4,7 +4,7 @@
|
||||
*/
|
||||
import applicationInformationToken from "./vars/application-information-token";
|
||||
import type { ApplicationInformation } from "./vars/application-information-token";
|
||||
import { bundledExtensionInjectionToken } from "../extensions/extension-discovery/bundled-extension-injection-token";
|
||||
import { bundledExtensionInjectionToken } from "../extensions/extension-discovery/bundled-extension-token";
|
||||
|
||||
// @experimental
|
||||
export {
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
|
||||
export type Falsey = false | 0 | "" | null | undefined;
|
||||
|
||||
interface Iterator<T> {
|
||||
interface Iterator<T> extends Iterable<T> {
|
||||
filter(fn: (val: T) => unknown): Iterator<T>;
|
||||
filterMap<U>(fn: (val: T) => Falsey | U): Iterator<U>;
|
||||
find(fn: (val: T) => unknown): T | undefined;
|
||||
@ -15,15 +15,16 @@ interface Iterator<T> {
|
||||
join(sep?: string): string;
|
||||
}
|
||||
|
||||
export function pipeline<T>(src: IterableIterator<T>): Iterator<T> {
|
||||
export function chain<T>(src: IterableIterator<T>): Iterator<T> {
|
||||
return {
|
||||
filter: (fn) => pipeline(filter(src, fn)),
|
||||
filterMap: (fn) => pipeline(filterMap(src, fn)),
|
||||
map: (fn) => pipeline(map(src, fn)),
|
||||
flatMap: (fn) => pipeline(flatMap(src, fn)),
|
||||
filter: (fn) => chain(filter(src, fn)),
|
||||
filterMap: (fn) => chain(filterMap(src, fn)),
|
||||
map: (fn) => chain(map(src, fn)),
|
||||
flatMap: (fn) => chain(flatMap(src, fn)),
|
||||
find: (fn) => find(src, fn),
|
||||
join: (sep) => join(src, sep),
|
||||
collect: (fn) => fn(src),
|
||||
[Symbol.iterator]: () => src,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
13
src/common/utils/with-concurrency-limit.ts
Normal file
13
src/common/utils/with-concurrency-limit.ts
Normal file
@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import plimit from "p-limit";
|
||||
|
||||
export type ConcurrencyLimiter = <Args extends any[], Res>(fn: (...args: Args) => Res) => (...args: Args) => Promise<Res>;
|
||||
|
||||
export function withConcurrencyLimit(limit: number): ConcurrencyLimiter {
|
||||
const limiter = plimit(limit);
|
||||
|
||||
return fn => (...args) => limiter(() => fn(...args));
|
||||
}
|
||||
@ -13,5 +13,5 @@ export interface BundledExtension {
|
||||
}
|
||||
|
||||
export const bundledExtensionInjectionToken = getInjectionToken<BundledExtension>({
|
||||
id: "bundled-extension-injection-token",
|
||||
id: "bundled-extension-path",
|
||||
});
|
||||
@ -6,5 +6,5 @@
|
||||
import { getInjectionToken } from "@ogre-tools/injectable";
|
||||
|
||||
export const extensionEntryPointNameInjectionToken = getInjectionToken<"main" | "renderer">({
|
||||
id: "extension-entry-point-name-injection-token",
|
||||
id: "extension-entry-point-name-token",
|
||||
});
|
||||
@ -12,8 +12,8 @@ import extensionInjectable from "./extension/extension.injectable";
|
||||
import loggerInjectable from "../../common/logger.injectable";
|
||||
import joinPathsInjectable from "../../common/path/join-paths.injectable";
|
||||
import getDirnameOfPathInjectable from "../../common/path/get-dirname.injectable";
|
||||
import { bundledExtensionInjectionToken } from "../extension-discovery/bundled-extension-injection-token";
|
||||
import { extensionEntryPointNameInjectionToken } from "./extension-entry-point-name-injection-token";
|
||||
import { bundledExtensionInjectionToken } from "../extension-discovery/bundled-extension-token";
|
||||
import { extensionEntryPointNameInjectionToken } from "./entry-point-name";
|
||||
|
||||
const extensionLoaderInjectable = getInjectable({
|
||||
id: "extension-loader",
|
||||
|
||||
@ -21,7 +21,7 @@ import type { Extension } from "./extension/extension.injectable";
|
||||
import type { Logger } from "../../common/logger";
|
||||
import type { JoinPaths } from "../../common/path/join-paths.injectable";
|
||||
import type { GetDirnameOfPath } from "../../common/path/get-dirname.injectable";
|
||||
import type { BundledExtension } from "../extension-discovery/bundled-extension-injection-token";
|
||||
import type { BundledExtension } from "../extension-discovery/bundled-extension-token";
|
||||
|
||||
const logModule = "[EXTENSIONS-LOADER]";
|
||||
|
||||
@ -37,7 +37,7 @@ interface Dependencies {
|
||||
getDirnameOfPath: GetDirnameOfPath;
|
||||
}
|
||||
|
||||
interface SemiLoadedExtension {
|
||||
interface ExtensionBeingActivated {
|
||||
instance: LensExtension;
|
||||
installedExtension: InstalledExtension;
|
||||
activated: Promise<void>;
|
||||
@ -268,13 +268,13 @@ export class ExtensionLoader {
|
||||
}
|
||||
|
||||
const installedExtension: InstalledExtension = {
|
||||
absolutePath: "irrelavent",
|
||||
absolutePath: "irrelevant",
|
||||
id: extension.manifest.name,
|
||||
isBundled: true,
|
||||
isCompatible: true,
|
||||
isEnabled: true,
|
||||
manifest: extension.manifest,
|
||||
manifestPath: "irrelavent",
|
||||
manifestPath: "irrelevant",
|
||||
};
|
||||
const instance = this.dependencies.createExtensionInstance(
|
||||
LensExtensionClass,
|
||||
@ -287,7 +287,7 @@ export class ExtensionLoader {
|
||||
instance,
|
||||
installedExtension,
|
||||
activated: instance.activate(),
|
||||
} as SemiLoadedExtension;
|
||||
} as ExtensionBeingActivated;
|
||||
} catch (err) {
|
||||
this.dependencies.logger.error(`${logModule}: error loading extension`, { ext: extension, err });
|
||||
|
||||
@ -297,7 +297,7 @@ export class ExtensionLoader {
|
||||
.filter(isDefined);
|
||||
}
|
||||
|
||||
protected async loadExtensions(extensions: SemiLoadedExtension[]): Promise<ExtensionLoading[]> {
|
||||
protected async loadExtensions(extensions: ExtensionBeingActivated[]): Promise<ExtensionLoading[]> {
|
||||
// We first need to wait until each extension's `onActivate` is resolved or rejected,
|
||||
// as this might register new catalog categories. Afterwards we can safely .enable the extension.
|
||||
await Promise.all(
|
||||
@ -359,7 +359,7 @@ export class ExtensionLoader {
|
||||
instance,
|
||||
installedExtension: extension,
|
||||
activated: instance.activate(),
|
||||
} as SemiLoadedExtension;
|
||||
} as ExtensionBeingActivated;
|
||||
} catch (err) {
|
||||
this.dependencies.logger.error(`${logModule}: error loading extension`, { ext: extension, err });
|
||||
}
|
||||
|
||||
@ -34,7 +34,7 @@ export class KubeconfigSyncManager {
|
||||
const seenIds = new Set<string>();
|
||||
|
||||
return (
|
||||
iter.pipeline(this.sources.values())
|
||||
iter.chain(this.sources.values())
|
||||
.flatMap(([entities]) => entities.get())
|
||||
.filter(entity => {
|
||||
const alreadySeen = seenIds.has(entity.getId());
|
||||
|
||||
49
src/main/cluster/request-api-resources.injectable.ts
Normal file
49
src/main/cluster/request-api-resources.injectable.ts
Normal file
@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import loggerInjectable from "../../common/logger.injectable";
|
||||
import type { KubeApiResource } from "../../common/rbac";
|
||||
import type { Cluster } from "../../common/cluster/cluster";
|
||||
import { requestApiVersionsInjectionToken } from "./request-api-versions";
|
||||
import { withConcurrencyLimit } from "../../common/utils/with-concurrency-limit";
|
||||
import requestKubeApiResourcesForInjectable from "./request-kube-api-resources-for.injectable";
|
||||
|
||||
export type RequestApiResources = (cluster: Cluster) => Promise<KubeApiResource[]>;
|
||||
|
||||
export interface KubeResourceListGroup {
|
||||
group: string;
|
||||
path: string;
|
||||
}
|
||||
|
||||
const requestApiResourcesInjectable = getInjectable({
|
||||
id: "request-api-resources",
|
||||
instantiate: (di): RequestApiResources => {
|
||||
const logger = di.inject(loggerInjectable);
|
||||
const apiVersionRequesters = di.injectMany(requestApiVersionsInjectionToken);
|
||||
const requestKubeApiResourcesFor = di.inject(requestKubeApiResourcesForInjectable);
|
||||
|
||||
return async (cluster) => {
|
||||
const requestKubeApiResources = withConcurrencyLimit(5)(requestKubeApiResourcesFor(cluster));
|
||||
|
||||
try {
|
||||
const requests = await Promise.all(apiVersionRequesters.map(fn => fn(cluster)));
|
||||
const resources = await Promise.all((
|
||||
requests
|
||||
.flat()
|
||||
.map(requestKubeApiResources)
|
||||
));
|
||||
|
||||
return resources.flat();
|
||||
} catch (error) {
|
||||
logger.error(`[LIST-API-RESOURCES]: failed to list api resources: ${error}`);
|
||||
|
||||
return [];
|
||||
}
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default requestApiResourcesInjectable;
|
||||
18
src/main/cluster/request-api-versions.ts
Normal file
18
src/main/cluster/request-api-versions.ts
Normal file
@ -0,0 +1,18 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { getInjectionToken } from "@ogre-tools/injectable";
|
||||
import type { Cluster } from "../../common/cluster/cluster";
|
||||
|
||||
export interface KubeResourceListGroup {
|
||||
group: string;
|
||||
path: string;
|
||||
}
|
||||
|
||||
export type RequestApiVersions = (cluster: Cluster) => Promise<KubeResourceListGroup[]>;
|
||||
|
||||
export const requestApiVersionsInjectionToken = getInjectionToken<RequestApiVersions>({
|
||||
id: "request-api-versions-token",
|
||||
});
|
||||
27
src/main/cluster/request-core-api-versions.injectable.ts
Normal file
27
src/main/cluster/request-core-api-versions.injectable.ts
Normal file
@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import type { V1APIVersions } from "@kubernetes/client-node";
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import k8sRequestInjectable from "../k8s-request.injectable";
|
||||
import { requestApiVersionsInjectionToken } from "./request-api-versions";
|
||||
|
||||
const requestCoreApiVersionsInjectable = getInjectable({
|
||||
id: "request-core-api-versions",
|
||||
instantiate: (di) => {
|
||||
const k8sRequest = di.inject(k8sRequestInjectable);
|
||||
|
||||
return async (cluster) => {
|
||||
const { versions } = await k8sRequest(cluster, "/api") as V1APIVersions;
|
||||
|
||||
return versions.map(version => ({
|
||||
group: version,
|
||||
path: `/api/${version}`,
|
||||
}));
|
||||
};
|
||||
},
|
||||
injectionToken: requestApiVersionsInjectionToken,
|
||||
});
|
||||
|
||||
export default requestCoreApiVersionsInjectable;
|
||||
@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import type { V1APIResourceList } from "@kubernetes/client-node";
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import type { Cluster } from "../../common/cluster/cluster";
|
||||
import type { KubeApiResource } from "../../common/rbac";
|
||||
import k8sRequestInjectable from "../k8s-request.injectable";
|
||||
import type { KubeResourceListGroup } from "./request-api-versions";
|
||||
|
||||
export type RequestKubeApiResources = (grouping: KubeResourceListGroup) => Promise<KubeApiResource[]>;
|
||||
|
||||
export type RequestKubeApiResourcesFor = (cluster: Cluster) => RequestKubeApiResources;
|
||||
|
||||
const requestKubeApiResourcesForInjectable = getInjectable({
|
||||
id: "request-kube-api-resources-for",
|
||||
instantiate: (di): RequestKubeApiResourcesFor => {
|
||||
const k8sRequest = di.inject(k8sRequestInjectable);
|
||||
|
||||
return (cluster) => async ({ group, path }) => {
|
||||
const { resources } = await k8sRequest(cluster, path) as V1APIResourceList;
|
||||
|
||||
return resources.map(resource => ({
|
||||
apiName: resource.name,
|
||||
kind: resource.kind,
|
||||
group,
|
||||
namespaced: resource.namespaced,
|
||||
}));
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default requestKubeApiResourcesForInjectable;
|
||||
30
src/main/cluster/request-non-core-api-versions.injectable.ts
Normal file
30
src/main/cluster/request-non-core-api-versions.injectable.ts
Normal file
@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import type { V1APIGroupList } from "@kubernetes/client-node";
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { chain } from "../../common/utils/iter";
|
||||
import k8sRequestInjectable from "../k8s-request.injectable";
|
||||
import { requestApiVersionsInjectionToken } from "./request-api-versions";
|
||||
|
||||
const requestNonCoreApiVersionsInjectable = getInjectable({
|
||||
id: "request-non-core-api-versions",
|
||||
instantiate: (di) => {
|
||||
const k8sRequest = di.inject(k8sRequestInjectable);
|
||||
|
||||
return async (cluster) => {
|
||||
const { groups } = await k8sRequest(cluster, "/apis") as V1APIGroupList;
|
||||
|
||||
return chain(groups.values())
|
||||
.filterMap(group => group.preferredVersion?.groupVersion && ({
|
||||
group: group.name,
|
||||
path: `/apis/${group.preferredVersion.groupVersion}`,
|
||||
}))
|
||||
.collect(v => [...v]);
|
||||
};
|
||||
},
|
||||
injectionToken: requestApiVersionsInjectionToken,
|
||||
});
|
||||
|
||||
export default requestNonCoreApiVersionsInjectable;
|
||||
@ -12,7 +12,7 @@ import createContextHandlerInjectable from "../context-handler/create-context-ha
|
||||
import { createClusterInjectionToken } from "../../common/cluster/create-cluster-injection-token";
|
||||
import authorizationReviewInjectable from "../../common/cluster/authorization-review.injectable";
|
||||
import listNamespacesInjectable from "../../common/cluster/list-namespaces.injectable";
|
||||
import createListApiResourcesInjectable from "../../common/cluster/request-api-resources.injectable";
|
||||
import createListApiResourcesInjectable from "../cluster/request-api-resources.injectable";
|
||||
import loggerInjectable from "../../common/logger.injectable";
|
||||
import detectorRegistryInjectable from "../cluster-detectors/detector-registry.injectable";
|
||||
import createVersionDetectorInjectable from "../cluster-detectors/create-version-detector.injectable";
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { extensionEntryPointNameInjectionToken } from "../../extensions/extension-loader/extension-entry-point-name-injection-token";
|
||||
import { extensionEntryPointNameInjectionToken } from "../../extensions/extension-loader/entry-point-name";
|
||||
|
||||
const extensionEntryPointNameInjectable = getInjectable({
|
||||
id: "extension-entry-point-name",
|
||||
@ -11,7 +11,7 @@ import lensProxyPortInjectable from "./lens-proxy/lens-proxy-port.injectable";
|
||||
|
||||
export type K8sRequest = (cluster: Cluster, path: string, options?: RequestPromiseOptions) => Promise<any>;
|
||||
|
||||
const k8SRequestInjectable = getInjectable({
|
||||
const k8sRequestInjectable = getInjectable({
|
||||
id: "k8s-request",
|
||||
|
||||
instantiate: (di) => {
|
||||
@ -34,4 +34,4 @@ const k8SRequestInjectable = getInjectable({
|
||||
},
|
||||
});
|
||||
|
||||
export default k8SRequestInjectable;
|
||||
export default k8sRequestInjectable;
|
||||
|
||||
@ -86,7 +86,7 @@ export class AddSecretDialog extends React.Component<AddSecretDialogProps> {
|
||||
};
|
||||
|
||||
private getDataFromFields = (fields: SecretTemplateField[] = [], processValue: (val: string) => string = (val => val)) => {
|
||||
return iter.pipeline(fields.values())
|
||||
return iter.chain(fields.values())
|
||||
.filterMap(({ key, value }) => (
|
||||
value
|
||||
? [key, processValue(value)] as const
|
||||
|
||||
@ -67,7 +67,7 @@ const NonInjectedCommandDialog = observer(({
|
||||
}
|
||||
};
|
||||
|
||||
const activeCommands = iter.pipeline(commands.get().values())
|
||||
const activeCommands = iter.chain(commands.get().values())
|
||||
.filter(command => {
|
||||
try {
|
||||
return command.isActive(context);
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { extensionEntryPointNameInjectionToken } from "../../extensions/extension-loader/extension-entry-point-name-injection-token";
|
||||
import { extensionEntryPointNameInjectionToken } from "../../extensions/extension-loader/entry-point-name";
|
||||
|
||||
const extensionEntryPointNameInjectable = getInjectable({
|
||||
id: "extension-entry-point-name",
|
||||
@ -27,7 +27,7 @@ export function cssNames(...classNames: IClassName[]): string {
|
||||
}
|
||||
}
|
||||
|
||||
return iter.pipeline(classNamesEnabled.entries())
|
||||
return iter.chain(classNamesEnabled.entries())
|
||||
.filter(([, isActive]) => !!isActive)
|
||||
.filterMap(([className]) => className.trim())
|
||||
.join(" ");
|
||||
|
||||
Loading…
Reference in New Issue
Block a user