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

Move lookupApi into apiManager (#3540)

This commit is contained in:
Sebastian Malton 2021-08-04 11:36:13 -04:00 committed by GitHub
parent 574aea40b4
commit ee158e33df
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 70 additions and 90 deletions

View File

@ -41,7 +41,7 @@ import { InstalledExtension, ExtensionDiscovery } from "../extensions/extension-
import type { LensExtensionId } from "../extensions/lens-extension";
import { installDeveloperTools } from "./developer-tools";
import { LensProtocolRouterMain } from "./protocol-handler";
import { disposer, getAppVersion, getAppVersionFromProxyServer } from "../common/utils";
import { disposer, getAppVersion, getAppVersionFromProxyServer, storedKubeConfigFolder } from "../common/utils";
import { bindBroadcastHandlers, ipcMainOn } from "../common/ipc";
import { startUpdateChecking } from "./app-updater";
import { IpcRendererNavigationEvents } from "../renderer/navigation/events";
@ -213,7 +213,7 @@ app.on("ready", async () => {
ipcMainOn(IpcRendererNavigationEvents.LOADED, async () => {
cleanup.push(pushCatalogToRenderer(catalogEntityRegistry));
await ensureDir(ClusterStore.storedKubeConfigFolder);
await ensureDir(storedKubeConfigFolder());
KubeconfigSyncManager.getInstance().startSync();
startUpdateChecking();
LensProtocolRouterMain.getInstance().rendererLoaded = true;

View File

@ -23,8 +23,9 @@ import type { KubeObjectStore } from "../kube-object.store";
import { action, observable, makeObservable } from "mobx";
import { autoBind, iter } from "../utils";
import { KubeApi, parseKubeApi } from "./kube-api";
import type { KubeApi } from "./kube-api";
import type { KubeObject } from "./kube-object";
import { IKubeObjectRef, parseKubeApi, createKubeApiURL } from "./kube-api-parse";
export class ApiManager {
private apis = observable.map<string, KubeApi<KubeObject>>();
@ -91,6 +92,45 @@ export class ApiManager {
getStore<S extends KubeObjectStore<KubeObject>>(api: string | KubeApi<KubeObject>): S | undefined {
return this.stores.get(this.resolveApi(api)?.apiBase) as S;
}
lookupApiLink(ref: IKubeObjectRef, parentObject: KubeObject): string {
const {
kind, apiVersion, name,
namespace = parentObject.getNs()
} = ref;
if (!kind) return "";
// search in registered apis by 'kind' & 'apiVersion'
const api = this.getApi(api => api.kind === kind && api.apiVersionWithGroup == apiVersion);
if (api) {
return api.getUrl({ namespace, name });
}
// lookup api by generated resource link
const apiPrefixes = ["/apis", "/api"];
const resource = kind.endsWith("s") ? `${kind.toLowerCase()}es` : `${kind.toLowerCase()}s`;
for (const apiPrefix of apiPrefixes) {
const apiLink = createKubeApiURL({ apiPrefix, apiVersion, name, namespace, resource });
if (this.getApi(apiLink)) {
return apiLink;
}
}
// resolve by kind only (hpa's might use refs to older versions of resources for example)
const apiByKind = this.getApi(api => api.kind === kind);
if (apiByKind) {
return apiByKind.getUrl({ name, namespace });
}
// otherwise generate link with default prefix
// resource still might exists in k8s, but api is not registered in the app
return createKubeApiURL({ apiVersion, name, namespace, resource });
}
}
export const apiManager = new ApiManager();

View File

@ -20,35 +20,21 @@
*/
import jsYaml from "js-yaml";
import { KubeObject } from "../kube-object";
import type { KubeJsonApiData } from "../kube-json-api";
import { apiBase } from "../index";
import { apiManager } from "../api-manager";
export const resourceApplierApi = {
annotations: [
"kubectl.kubernetes.io/last-applied-configuration"
],
async update<K extends KubeObject>(resource: object | string): Promise<K | null> {
async update(resource: object | string): Promise<KubeJsonApiData | null> {
if (typeof resource === "string") {
resource = jsYaml.safeLoad(resource);
}
return apiBase
.post<KubeJsonApiData[]>("/stack", { data: resource })
.then(data => {
const items = data.map(obj => {
const api = apiManager.getApiByKind(obj.kind, obj.apiVersion);
const [data = null] = await apiBase.post<KubeJsonApiData[]>("/stack", { data: resource });
if (api) {
return new api.objectConstructor(obj);
} else {
return new KubeObject(obj);
}
});
return items[0] as K ?? null;
});
return data;
}
};

View File

@ -21,9 +21,7 @@
// Parse kube-api path and get api-version, group, etc.
import type { KubeObject } from "./kube-object";
import { splitArray } from "../../common/utils";
import { apiManager } from "./api-manager";
import { isDebugging } from "../../common/vars";
import logger from "../../main/logger";
import { inspect } from "util";
@ -159,42 +157,3 @@ export function createKubeApiURL(ref: IKubeApiLinkRef): string {
.filter(v => v)
.join("/");
}
export function lookupApiLink(ref: IKubeObjectRef, parentObject: KubeObject): string {
const {
kind, apiVersion, name,
namespace = parentObject.getNs()
} = ref;
if (!kind) return "";
// search in registered apis by 'kind' & 'apiVersion'
const api = apiManager.getApi(api => api.kind === kind && api.apiVersionWithGroup == apiVersion);
if (api) {
return api.getUrl({ namespace, name });
}
// lookup api by generated resource link
const apiPrefixes = ["/apis", "/api"];
const resource = kind.endsWith("s") ? `${kind.toLowerCase()}es` : `${kind.toLowerCase()}s`;
for (const apiPrefix of apiPrefixes) {
const apiLink = createKubeApiURL({ apiPrefix, apiVersion, name, namespace, resource });
if (apiManager.getApi(apiLink)) {
return apiLink;
}
}
// resolve by kind only (hpa's might use refs to older versions of resources for example)
const apiByKind = apiManager.getApi(api => api.kind === kind);
if (apiByKind) {
return apiByKind.getUrl({ name, namespace });
}
// otherwise generate link with default prefix
// resource still might exists in k8s, but api is not registered in the app
return createKubeApiURL({ apiVersion, name, namespace, resource });
}

View File

@ -56,11 +56,6 @@ export interface IKubeApiOptions<T extends KubeObject> {
checkPreferredVersion?: boolean;
}
export interface KubeApiListOptions {
namespace?: string;
reqInit?: RequestInit;
}
export interface IKubeApiQueryParams {
watch?: boolean | number;
resourceVersion?: string;
@ -506,5 +501,3 @@ export class KubeApi<T extends KubeObject> {
}
}
}
export * from "./kube-api-parse";

View File

@ -287,14 +287,14 @@ export class KubeObject<Metadata extends KubeObjectMetadata = KubeObjectMetadata
}
// use unified resource-applier api for updating all k8s objects
async update<K extends KubeObject>(data: Partial<K>): Promise<K> {
async update(data: Partial<this>): Promise<KubeJsonApiData | null> {
for (const field of KubeObject.nonEditableFields) {
if (!_.isEqual(_.get(this, field), _.get(data, field))) {
throw new Error(`Failed to update Kube Object: ${field} has been modified`);
}
}
return resourceApplierApi.update<K>({
return resourceApplierApi.update({
...this.toPlainObject(),
...data,
});

View File

@ -33,7 +33,7 @@ import { boundMethod, cssNames, prevDefault } from "../../utils";
import type { ItemObject } from "../../item.store";
import { Spinner } from "../spinner";
import { ThemeStore } from "../../theme.store";
import { lookupApiLink } from "../../api/kube-api";
import { apiManager } from "../../api/api-manager";
import { kubeSelectedUrlParam, showDetails } from "../kube-detail-params";
import { kubeWatchApi } from "../../api/kube-watch-api";
@ -106,7 +106,7 @@ export class ClusterIssues extends React.Component<Props> {
age: getAge(),
message,
kind,
selfLink: lookupApiLink(involvedObject, error),
selfLink: apiManager.lookupApiLink(involvedObject, error),
});
});

View File

@ -30,7 +30,7 @@ import type { KubeObjectDetailsProps } from "../kube-object-details";
import { cssNames } from "../../utils";
import { HorizontalPodAutoscaler, HpaMetricType, IHpaMetric } from "../../api/endpoints/hpa.api";
import { Table, TableCell, TableHead, TableRow } from "../table";
import { lookupApiLink } from "../../api/kube-api";
import { apiManager } from "../../api/api-manager";
import { KubeObjectMeta } from "../kube-object-meta";
import { getDetailsUrl } from "../kube-detail-params";
@ -55,7 +55,7 @@ export class HpaDetails extends React.Component<HpaDetailsProps> {
case HpaMetricType.Object:
const { target } = metric.object;
const { kind, name } = target;
const objectUrl = getDetailsUrl(lookupApiLink(target, hpa));
const objectUrl = getDetailsUrl(apiManager.lookupApiLink(target, hpa));
return (
<>
@ -108,7 +108,7 @@ export class HpaDetails extends React.Component<HpaDetailsProps> {
<DrawerItem name="Reference">
{scaleTargetRef && (
<Link to={getDetailsUrl(lookupApiLink(scaleTargetRef, hpa))}>
<Link to={getDetailsUrl(apiManager.lookupApiLink(scaleTargetRef, hpa))}>
{scaleTargetRef.kind}/{scaleTargetRef.name}
</Link>
)}

View File

@ -30,7 +30,7 @@ import type { KubeObjectDetailsProps } from "../kube-object-details";
import type { KubeEvent } from "../../api/endpoints/events.api";
import { KubeObjectMeta } from "../kube-object-meta";
import { Table, TableCell, TableHead, TableRow } from "../table";
import { lookupApiLink } from "../../api/kube-api";
import { apiManager } from "../../api/api-manager";
import { LocaleDate } from "../locale-date";
import { getDetailsUrl } from "../kube-detail-params";
@ -82,7 +82,7 @@ export class EventDetails extends React.Component<Props> {
</TableHead>
<TableRow>
<TableCell>
<Link to={getDetailsUrl(lookupApiLink(involvedObject, event))}>
<Link to={getDetailsUrl(apiManager.lookupApiLink(involvedObject, event))}>
{name}
</Link>
</TableCell>

View File

@ -35,7 +35,7 @@ import { Tooltip } from "../tooltip";
import { Link } from "react-router-dom";
import { cssNames, IClassName, stopPropagation } from "../../utils";
import { Icon } from "../icon";
import { lookupApiLink } from "../../api/kube-api";
import { apiManager } from "../../api/api-manager";
import { eventsURL } from "../../../common/routes";
import { getDetailsUrl } from "../kube-detail-params";
@ -196,7 +196,7 @@ export class Events extends React.Component<Props> {
)
},
event.getNs(),
<Link key="link" to={getDetailsUrl(lookupApiLink(involvedObject, event))} onClick={stopPropagation}>
<Link key="link" to={getDetailsUrl(apiManager.lookupApiLink(involvedObject, event))} onClick={stopPropagation}>
{involvedObject.kind}: {involvedObject.name}
</Link>,
event.getSource(),

View File

@ -26,7 +26,7 @@ import { observer } from "mobx-react";
import { EndpointSubset, Endpoint, EndpointAddress} from "../../api/endpoints";
import { Table, TableCell, TableHead, TableRow } from "../table";
import { boundMethod } from "../../utils";
import { lookupApiLink } from "../../api/kube-api";
import { apiManager } from "../../api/api-manager";
import { Link } from "react-router-dom";
import { getDetailsUrl } from "../kube-detail-params";
@ -92,7 +92,7 @@ export class EndpointSubsetList extends React.Component<Props> {
<TableCell className="name">{address.hostname}</TableCell>
<TableCell className="target">
{ address.targetRef && (
<Link to={getDetailsUrl(lookupApiLink(address.getTargetRef(), endpoint))}>
<Link to={getDetailsUrl(apiManager.lookupApiLink(address.getTargetRef(), endpoint))}>
{address.targetRef.name}
</Link>
)}

View File

@ -35,7 +35,7 @@ import { jobStore } from "./job.store";
import type { KubeObjectDetailsProps } from "../kube-object-details";
import { getMetricsForJobs, IPodMetrics, Job } from "../../api/endpoints";
import { PodDetailsList } from "../+workloads-pods/pod-details-list";
import { lookupApiLink } from "../../api/kube-api";
import { apiManager } from "../../api/api-manager";
import { KubeObjectMeta } from "../kube-object-meta";
import { makeObservable, observable } from "mobx";
import { podMetricTabs, PodCharts } from "../+workloads-pods/pod-charts";
@ -117,7 +117,7 @@ export class JobDetails extends React.Component<Props> {
{
ownerRefs.map(ref => {
const { name, kind } = ref;
const detailsUrl = getDetailsUrl(lookupApiLink(ref, job));
const detailsUrl = getDetailsUrl(apiManager.lookupApiLink(ref, job));
return (
<p key={name}>

View File

@ -35,7 +35,7 @@ import { cssNames, stopPropagation } from "../../utils";
import toPairs from "lodash/toPairs";
import startCase from "lodash/startCase";
import kebabCase from "lodash/kebabCase";
import { lookupApiLink } from "../../api/kube-api";
import { apiManager } from "../../api/api-manager";
import { KubeObjectStatusIcon } from "../kube-object-status-icon";
import { Badge } from "../badge";
import type { PodsRouteParams } from "../../../common/routes";
@ -135,7 +135,7 @@ export class Pods extends React.Component<Props> {
pod.getRestartsCount(),
pod.getOwnerRefs().map(ref => {
const { kind, name } = ref;
const detailsLink = getDetailsUrl(lookupApiLink(ref, pod));
const detailsLink = getDetailsUrl(apiManager.lookupApiLink(ref, pod));
return (
<Badge flat key={name} className="owner" tooltip={name}>

View File

@ -105,7 +105,7 @@ export class CreateResource extends React.Component<Props> {
await Promise.all(
resources.map(data => {
return resourceApplierApi.update(data)
.then(item => createdResources.push(item.getName()))
.then(item => createdResources.push(item.metadata.name))
.catch((err: JsonApiErrorParsed) => errors.push(err.toString()));
})
);

View File

@ -22,7 +22,7 @@
import React from "react";
import type { KubeMetaField, KubeObject } from "../../api/kube-object";
import { DrawerItem, DrawerItemLabels } from "../drawer";
import { lookupApiLink } from "../../api/kube-api";
import { apiManager } from "../../api/api-manager";
import { Link } from "react-router-dom";
import { KubeObjectStatusIcon } from "../kube-object-status-icon";
import { LocaleDate } from "../locale-date";
@ -93,7 +93,7 @@ export class KubeObjectMeta extends React.Component<KubeObjectMetaProps> {
{
ownerRefs.map(ref => {
const { name, kind } = ref;
const ownerDetailsUrl = getDetailsUrl(lookupApiLink(ref, object));
const ownerDetailsUrl = getDetailsUrl(apiManager.lookupApiLink(ref, object));
return (
<p key={name}>

View File

@ -27,7 +27,8 @@ import { KubeObject, KubeStatus } from "./api/kube-object";
import type { IKubeWatchEvent } from "./api/kube-watch-api";
import { ItemStore } from "./item.store";
import { apiManager } from "./api/api-manager";
import { ensureObjectSelfLink, IKubeApiQueryParams, KubeApi, parseKubeApi } from "./api/kube-api";
import { ensureObjectSelfLink, IKubeApiQueryParams, KubeApi } from "./api/kube-api";
import { parseKubeApi } from "./api/kube-api-parse";
import type { KubeJsonApiData } from "./api/kube-json-api";
import { Notifications } from "./components/notifications";
@ -279,7 +280,8 @@ export abstract class KubeObjectStore<T extends KubeObject> extends ItemStore<T>
}
async update(item: T, data: Partial<T>): Promise<T> {
const newItem = await item.update(data);
const rawItem = await item.update(data);
const newItem = new this.api.objectConstructor(rawItem);
ensureObjectSelfLink(this.api, newItem);